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

Compare commits

...

195 Commits

Author SHA1 Message Date
github-actions[bot]
e1406875aa Update VERSION to v0.2.3 (from DMS) 2025-10-25 18:56:06 +00:00
github-actions[bot]
6ab96898a3 i18n: update translations 2025-10-25 18:35:07 +00:00
github-actions[bot]
7973b2f3da i18n: update source strings from codebase 2025-10-25 18:35:00 +00:00
bbedward
90284625af lock/greetd: fix 12-hour time 2025-10-25 14:34:25 -04:00
bbedward
6b3442512a net: only show eth/wifi on networkmanager backend 2025-10-25 14:23:12 -04:00
github-actions[bot]
f9208136af i18n: update source strings from codebase 2025-10-25 18:03:54 +00:00
bbedward
9895fbde0d dock: dynamic indicator positions 2025-10-25 14:03:20 -04:00
bbedward
30370e4985 lock/greeter: fixed clock widths 2025-10-25 14:00:02 -04:00
github-actions[bot]
7a45f370b5 i18n: update source strings from codebase 2025-10-25 16:43:28 +00:00
Massimo Branchini
38068eeaac better checks before saying SUCCESS (#550) 2025-10-25 12:43:09 -04:00
bbedward
62df30ed6c apps: fix sorting and reactivity 2025-10-25 12:42:54 -04:00
bbedward
7e75c9e510 dankbar: fix widget hover effects 2025-10-25 12:42:54 -04:00
github-actions[bot]
42f9edf566 i18n: update translations 2025-10-25 15:48:30 +00:00
github-actions[bot]
c72b6144a5 i18n: update source strings from codebase 2025-10-25 15:48:23 +00:00
bbedward
032777e32e net: switch to native VPN backend 2025-10-25 11:47:46 -04:00
Jack Grahn
607b5320fd Add clipboard history command to niri spawn-at-startup options (#545) 2025-10-25 10:08:48 -04:00
Massimo Branchini
959766b265 external system update trigger (#546) 2025-10-25 10:07:56 -04:00
github-actions[bot]
9774991b56 i18n: update translations 2025-10-25 06:01:33 +00:00
github-actions[bot]
e1587995d0 i18n: update source strings from codebase 2025-10-25 06:01:26 +00:00
bbedward
08e6e22046 net: lose fail tracking 2025-10-25 02:00:53 -04:00
github-actions[bot]
454d8bdc88 i18n: update source strings from codebase 2025-10-25 03:59:51 +00:00
bbedward
d0ae7431eb net: updates to accomodate iwd + other backends 2025-10-24 23:59:23 -04:00
purian23
d88dc17b21 Format spec 2025-10-24 22:28:27 -04:00
purian23
705f569571 Update dms-greeter colors path 2025-10-24 19:01:58 -04:00
purian23
abc7badfa9 Print the final message 2025-10-24 18:44:28 -04:00
purian23
e8c2469227 Simplify upgrade message 2025-10-24 18:37:30 -04:00
purian23
1e5e8cd246 Fix scope 2025-10-24 18:26:26 -04:00
purian23
adc81cfb95 Moar Copr DMS restart logic 2025-10-24 18:18:21 -04:00
github-actions[bot]
e175fa64cb i18n: update translations 2025-10-24 21:21:52 +00:00
bbedward
f9994d0e42 add turkish 2025-10-24 17:21:15 -04:00
github-actions[bot]
3e5d1c514a i18n: update translations 2025-10-24 21:17:00 +00:00
purian23
6310394034 Add 'dms restart' to Copr upgrades 2025-10-24 17:16:29 -04:00
purian23
f32596053b Update Copr default dir to usr/share 2025-10-24 13:27:48 -04:00
bbedward
cf4a6969d3 plugins: fix set ToggleSetting not saving
fixes #541
2025-10-24 13:12:57 -04:00
github-actions[bot]
0918412916 i18n: update translations 2025-10-24 16:38:47 +00:00
github-actions[bot]
41b1718587 i18n: update source strings from codebase 2025-10-24 16:38:43 +00:00
bbedward
ca2acbc704 niri: ability to blur wallpaper on overview + add a separate layer for
blurred wallpapers
2025-10-24 12:37:57 -04:00
bbedward
1abd3ef8b1 greeter: search /usr/share path for qml files 2025-10-24 09:10:38 -04:00
bbedward
cedba3770c clock: baseline text relative to date
fixes #535
2025-10-24 08:47:09 -04:00
bbedward
f733be1fd1 lock/greeter: seconds precision on clock
fixes #540
2025-10-24 08:38:41 -04:00
github-actions[bot]
01e02232d7 i18n: update source strings from codebase 2025-10-24 03:16:41 +00:00
bbedward
771920b38b dankbar: fix focusedapp & media text clipping
fixes #537
2025-10-23 23:15:53 -04:00
bbedward
0f55bbc148 dankbar: remove hardcoded font weights
fixes #539
2025-10-23 23:11:20 -04:00
bbedward
ab4e9646ad workspace: don't wrap on mousewheel
fixes #538
2025-10-23 23:04:17 -04:00
bbedward
884b73599a dd: add file name to wallpaper tab 2025-10-23 22:56:53 -04:00
bbedward
492c0e7ef7 dankbar: fix separator 2025-10-23 22:44:49 -04:00
purian23
0865ae000b Remove Notepad indicator 2025-10-23 22:13:53 -04:00
purian23
049c9b44e4 Add req accountsservice to Copr 2025-10-23 19:04:49 -04:00
github-actions[bot]
199edd3771 i18n: update translations 2025-10-23 22:17:51 +00:00
bbedward
8806217d25 gh: use workflow_run trigger for copr 2025-10-23 18:17:09 -04:00
github-actions[bot]
cc1588debd Update VERSION to v0.2.2 (from DMS) 2025-10-23 22:11:22 +00:00
github-actions[bot]
d2ba4b32fe i18n: update translations 2025-10-23 20:53:46 +00:00
github-actions[bot]
b3d5054966 i18n: update source strings from codebase 2025-10-23 20:53:42 +00:00
bbedward
57a921425c settings: about About page 2025-10-23 16:53:00 -04:00
github-actions[bot]
061aaeb933 i18n: update source strings from codebase 2025-10-23 20:14:00 +00:00
bbedward
0c7af9c740 meta: log level re-work 2025-10-23 16:13:27 -04:00
bbedward
d5c4b990dc accessibility: widen click targets to bar edge 2025-10-23 15:51:59 -04:00
Aleksandr Lebedev
a650a79dfc Fixed bugs in Workspace Switcher (#534)
* Fixed bugs with workspace switcher:

- fixed bug that, when moving existing windows/moving focus from one
window to another, information about positions and active windows on
workspace switcher was not updated
- fixed bug that hovering/clicking on app icons didn't work, because
of missplaced MouseArea

* Added comment
2025-10-23 14:46:29 -04:00
github-actions[bot]
7ac6e94348 i18n: update translations 2025-10-23 18:31:58 +00:00
github-actions[bot]
b4abdf3d51 i18n: update source strings from codebase 2025-10-23 18:31:54 +00:00
bbedward
b59b87d84e bluetooth+plugins: some repairs for bad references and dialogs plugins: switch to ID-based references 2025-10-23 14:31:12 -04:00
github-actions[bot]
799ae1a20e i18n: update source strings from codebase 2025-10-23 16:58:27 +00:00
bbedward
1e58e69c59 fix dms subscription 2025-10-23 12:57:50 -04:00
github-actions[bot]
c667bab5ca i18n: update source strings from codebase 2025-10-23 16:25:01 +00:00
bbedward
2a744fb174 battery: hide secondary text on no battery 2025-10-23 12:24:20 -04:00
bbedward
a9744a0cad readme: document accountsservice dependency 2025-10-23 12:15:13 -04:00
github-actions[bot]
0aab22f242 i18n: update translations 2025-10-23 15:55:55 +00:00
github-actions[bot]
b0f65225a9 i18n: update source strings from codebase 2025-10-23 15:55:50 +00:00
bbedward
1311da7258 bluetooth: integrate with DMS API v9 - Supports proper pairing with an agent & pin, passcode, etc. 2025-10-23 11:55:07 -04:00
bbedward
61d68b1f76 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-23 11:54:56 -04:00
github-actions[bot]
5dd1769536 i18n: update translations 2025-10-23 15:19:35 +00:00
Zsolt Donca
a45a9bda9e Fixed path to dms-colors.json in Modules/Greetd/README.md (#532)
Fixed the path to dms-colors.json, as the previous path did not actually exist, resulting in a broken symbolic link and the colors not actually being right in the login screen.
2025-10-23 11:19:02 -04:00
bbedward
4e43c797e2 niri: improve toplevel sorting 2025-10-23 10:33:53 -04:00
github-actions[bot]
beab1a7b01 i18n: update translations 2025-10-23 13:31:18 +00:00
bbedward
85c00a9c4e dankbar: prevent double widget instances for horiz/vertical 2025-10-23 09:30:37 -04:00
Moraxyc Xu
b2e5565110 nix: use standard way to remove option (#529) 2025-10-22 22:46:15 -04:00
github-actions[bot]
95785afec9 i18n: update source strings from codebase 2025-10-23 02:44:56 +00:00
bbedward
d9d16eccfe displays: default show on last display to true, always 2025-10-22 22:44:26 -04:00
github-actions[bot]
26900c9b62 i18n: update translations 2025-10-23 02:40:18 +00:00
github-actions[bot]
8113ddc809 i18n: update source strings from codebase 2025-10-23 02:40:13 +00:00
bbedward
3cd6a1a558 displays: add "show on last display" for some components.
- Lets the components migrate when un-docked to the otehr monitor,
  basically
2025-10-22 22:38:59 -04:00
purian23
9128141be0 Release tag to Copr 2025-10-22 21:35:36 -04:00
bbedward
0b0af20a84 matugen: add dark/light kcolorschemes 2025-10-22 19:02:38 -04:00
github-actions[bot]
225144cb46 i18n: update source strings from codebase 2025-10-22 20:55:17 +00:00
bbedward
bbe802037e lock: send lockerReady after frame rendered 2025-10-22 16:54:34 -04:00
bbedward
1db4e92779 suppress brightness OSD when operating from cc 2025-10-22 16:38:20 -04:00
github-actions[bot]
072883dcd4 i18n: update source strings from codebase 2025-10-22 20:31:27 +00:00
bbedward
a25e929200 cc: allow pinning brightness device per-monitor 2025-10-22 16:30:51 -04:00
bbedward
6c4d27be8a dock: fix indicator positions 2025-10-22 14:39:21 -04:00
github-actions[bot]
8825382502 i18n: update translations 2025-10-22 18:08:52 +00:00
github-actions[bot]
9ce3c5bd73 i18n: update source strings from codebase 2025-10-22 18:08:46 +00:00
bbedward
771346c8fa dock: add dot indicator style 2025-10-22 14:08:13 -04:00
github-actions[bot]
a56b2d6a9f Update VERSION to v0.2.1 (from DMS) 2025-10-22 17:38:31 +00:00
github-actions[bot]
342f980bad i18n: update translations 2025-10-22 17:35:23 +00:00
Roni Laukkarinen
dbc1bdeb3b Fix WorkspaceSwitcher crash: replace undefined parentScreen?.name with screenName (#527) 2025-10-22 13:34:39 -04:00
github-actions[bot]
1cb10e879e Update VERSION to v0.2.0 (from DMS) 2025-10-22 16:14:40 +00:00
bbedward
d90dd5288b notif: improve indicator 2025-10-22 11:14:54 -04:00
bokicoder
e55a517dae update & cleanup flake (#526) 2025-10-22 10:59:23 -04:00
github-actions[bot]
378def1fa3 i18n: update source strings from codebase 2025-10-22 14:12:26 +00:00
bbedward
ea7676c98e runningapps: fix context menu on vertical bar 2025-10-22 10:11:51 -04:00
github-actions[bot]
b9b15568b4 i18n: update translations 2025-10-22 13:43:40 +00:00
github-actions[bot]
701d8cbd8a i18n: update source strings from codebase 2025-10-22 13:43:34 +00:00
bbedward
bd525763de plugins: improve update/tooltips/UI 2025-10-22 09:42:55 -04:00
github-actions[bot]
479868718e i18n: update source strings from codebase 2025-10-22 04:14:10 +00:00
bbedward
951136bc4c dankbar: enhance widget click targets
- Fitt's law stuff, whole height on horiz, whole width in vertical
- Probably missed stuff or breaks stuff, pretty big refactor
2025-10-22 00:12:41 -04:00
github-actions[bot]
8ab25ef8e4 i18n: update translations 2025-10-22 03:16:45 +00:00
github-actions[bot]
a11cd9b0df i18n: update source strings from codebase 2025-10-22 03:16:39 +00:00
bbedward
1e72733e81 dankdash: add wallpaper selector + IPC targets 2025-10-21 23:15:10 -04:00
bbedward
967b7d05de Revert 2025-10-21 23:13:54 -04:00
github-actions[bot]
90bc890190 i18n: update translations 2025-10-22 03:10:19 +00:00
purian23
647c358b72 feat: Wallpapers built into the Media Hub
- Thanks @TaylanTatli for the inspiration
2025-10-21 23:09:10 -04:00
github-actions[bot]
2a89885437 i18n: update translations 2025-10-21 21:46:51 +00:00
github-actions[bot]
47cc43185d i18n: update source strings from codebase 2025-10-21 21:46:47 +00:00
bbedward
aa6e09ed3e notifications: trigger first action on left click in popup, if present 2025-10-21 17:46:09 -04:00
github-actions[bot]
c7bc3d6f3b i18n: update source strings from codebase 2025-10-21 21:06:54 +00:00
bbedward
0b68bf7c07 greeter: don't use a session lock 2025-10-21 17:06:26 -04:00
Ignacio Serrano
3274ef5e3e fix: night mode is now always available (#524) 2025-10-21 16:40:56 -04:00
github-actions[bot]
f93a00c8d4 i18n: update source strings from codebase 2025-10-21 19:56:15 +00:00
Bruno Cesar Rocha
92bcb83b16 Add support for material icons on launcher (#521)
- Add support for using material icons on launcher items (for plugins)
- Allow plugins to omit icons
- Update plugin docs
- add developer note so the next one working on launcher will not spend
  hours debugging the wrong place as I just did :)
2025-10-21 15:55:45 -04:00
bbedward
4e0c813db7 readme update 2025-10-21 14:36:56 -04:00
github-actions[bot]
d4509c80b7 i18n: update translations 2025-10-21 18:28:27 +00:00
github-actions[bot]
c9313df3a4 i18n: update source strings from codebase 2025-10-21 18:28:23 +00:00
bbedward
9b6fb29d46 nm: updates for NM agent in DMS API v7 2025-10-21 14:27:42 -04:00
github-actions[bot]
50ce5cf257 i18n: update translations 2025-10-21 18:08:50 +00:00
github-actions[bot]
b19e5b3b40 i18n: update source strings from codebase 2025-10-21 18:08:46 +00:00
bbedward
c07ba3f737 hyprland: add overview 2025-10-21 14:02:53 -04:00
github-actions[bot]
eff5f60264 i18n: update translations 2025-10-21 14:14:46 +00:00
github-actions[bot]
355b2e16b4 i18n: update source strings from codebase 2025-10-21 14:14:42 +00:00
bbedward
c389101a10 workflow: add term sync with poeditor 2025-10-21 10:13:56 -04:00
bbedward
4aa0b3d0fc theme: sync color scheme after matugen finishes 2025-10-21 10:04:28 -04:00
bbedward
322f1415f6 gamma: fix persistence of night mode auto-location 2025-10-21 08:40:27 -04:00
bokicoder
0d57691e38 add color picking support option (#516) 2025-10-21 08:10:34 -04:00
purian23
507b516f89 fix: Privacy audio null property 2025-10-20 21:02:33 -04:00
bbedward
7bf73ab14d gamma/nightmode: use dms V6 implementation - Scraps gammastep depednency 2025-10-20 18:24:57 -04:00
bokicoder
9a305355c2 disable sleep inhibitor when lock-before-suspend disabled (#512) 2025-10-20 13:03:27 -04:00
github-actions[bot]
6ac382a25f i18n: update translations 2025-10-20 16:16:16 +00:00
bbedward
e02b255442 system tray: Add DMS_HIDE_TRAYIDs 2025-10-20 12:15:52 -04:00
github-actions[bot]
d978740d66 i18n: update translations 2025-10-20 15:47:16 +00:00
bbedward
62b7492e9f portal: handle profile setting errors 2025-10-20 11:43:14 -04:00
bbedward
c2f42f3f69 bar: fix border with radius 2025-10-20 11:34:46 -04:00
github-actions[bot]
2c4a40e778 i18n: update translations 2025-10-20 15:15:51 +00:00
bbedward
635fcad416 Revert "lock: terminate fprint pam session when using password"
This reverts commit 7bf7d0afae.
2025-10-20 11:15:26 -04:00
github-actions[bot]
2c92f830d1 i18n: update translations 2025-10-20 14:05:10 +00:00
bbedward
4924f3e55a cc: scrap bluetooth device icons 2025-10-20 10:04:36 -04:00
bbedward
53306165e1 bar: fix border canvas 2025-10-20 09:43:49 -04:00
github-actions[bot]
1ebcdaaf62 i18n: update translations 2025-10-20 13:22:56 +00:00
bbedward
078ef203b6 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-20 09:22:26 -04:00
bbedward
59669d8b7f i18n: add some terms 2025-10-20 09:22:03 -04:00
github-actions[bot]
d38b98459a i18n: update translations 2025-10-20 13:07:57 +00:00
bbedward
f54e53b8a0 theme: fix gtk light
- I just had to rip gtk.css from adw-gtk3 for it to apply colors idk
2025-10-20 09:07:07 -04:00
bbedward
851d47213c themes: change gtk theme path to adw-gtk3 + libadwaita for gtk4 2025-10-19 23:50:18 -04:00
bbedward
ad778b5d81 wallpaper: respect fill mode on lock + greeter 2025-10-19 23:39:21 -04:00
bbedward
0cb081a6d0 lock: bypass custom commands in ipc 2025-10-19 23:15:32 -04:00
bbedward
daf3525e80 show hidden files for wallpapers 2025-10-19 22:44:21 -04:00
Mattias
b35198c710 feat: Only colour Bluetooth icon in DankBar if a device is connected (#497) 2025-10-19 22:35:31 -04:00
Mattias
1feb77aadb chore: Tidy up superfluous whitespace (#503) 2025-10-19 22:35:17 -04:00
purian23
d6b690ae2f feat: Updated DMS Animations
- Based on Material 3 Expressive
- Now features custom timers options
- Thanks to Soramane/Caelestia for converting Google's Material 3 Expressive curves
2025-10-19 21:42:18 -04:00
purian23
b1ae246c86 Update Greeter ReadMe 2025-10-19 19:14:13 -04:00
Roni Laukkarinen
4ceb5f13e5 Dock: Support ultrawide monitors and wide docks instead of hardcoded 1200px dock boundary (#502) 2025-10-19 13:14:31 -05:00
bbedward
64960e4dcd wallpaper: support different fill modes 2025-10-19 12:49:57 -04:00
Mattias
e1c180a13f feat: Indicate when the mic is muted in the Privacy Indicator (#495)
* feat: Indicate when the mic is muted in the Privacy Indicator

* chore: tidy up
2025-10-19 11:36:01 -05:00
radicaltray
86a0fd409a fix: fix ghostty config reload on nixos (#499) 2025-10-19 11:35:28 -05:00
purian23
5a32398446 Update more sidebar settings nav 2025-10-18 23:10:08 -04:00
purian23
bcb22ec265 feat: Keybaord Nav to Category Settings
- fix: Escape key to exit settings
2025-10-18 20:45:01 -04:00
purian23
8719dcf98f fix: System Updater focus 2025-10-18 19:33:54 -04:00
github-actions[bot]
9b4b2f75c1 i18n: update translations 2025-10-18 16:53:55 +00:00
bbedward
8acde3a347 lock: fix passing screenName to surface 2025-10-18 12:53:19 -04:00
purian23
7c1e247ef8 feat: Group by App on Running Apps 2025-10-17 22:24:47 -04:00
purian23
f4cd27d316 Update battery logic to fix NaN / Infinity % 2025-10-17 22:21:12 -04:00
github-actions[bot]
205c43181b Update VERSION to v0.1.17 (from DMS) 2025-10-17 20:03:12 +00:00
purian23
05a72abf41 Update Copr Architecture logic 2025-10-17 15:39:09 -04:00
purian23
14262ba510 Silence upgrade noise 2025-10-17 11:05:54 -04:00
github-actions[bot]
d847b1e09c i18n: update translations 2025-10-17 12:48:01 +00:00
bbedward
0086e42a86 i18n: don't rely on po webhooks 2025-10-17 08:47:18 -04:00
bbedward
7474d5a7bf poeditor: disable workflow
They paywalled it even for open source
2025-10-17 08:42:56 -04:00
bbedward
5696a36115 ws: disable window scrolling toggle when not niri 2025-10-17 08:40:26 -04:00
maggster165
3cdc1a9c81 Add Workspace Indicator scrolling (#475) 2025-10-17 08:37:28 -04:00
Massimo Branchini
b095fb9005 small fix: initial space does not allow correct alignment (#477) 2025-10-17 08:37:18 -04:00
bbedward
ce6c16214c lock: allow custom lock command 2025-10-17 08:36:11 -04:00
bbedward
b6f7f2734e network: hide eth/wifi preference when apiVersion < 5 2025-10-17 08:23:56 -04:00
bbedward
4db55e4d77 Bump expected API back down, doesn't really matter 2025-10-17 08:17:16 -04:00
Massimo Branchini
b21f6e80b3 enhancement: managed NetworkManager ethernet configurations connectio… (#473)
* enhancement: managed NetworkManager ethernet configurations connection from control panel

* server API minimal version
2025-10-17 08:05:42 -04:00
bokicoder
a804fb849e Update readme (#471) 2025-10-17 07:01:12 -04:00
purian23
4ca91cd9f7 SELinux & Path DIR updates 2025-10-17 01:28:59 -04:00
purian23
16e1b587b4 Added logic for PAM users / SELinux 2025-10-16 23:56:25 -04:00
bbedward
5e2756d200 theme: don't need portal for light/dark 2025-10-16 23:38:13 -04:00
bbedward
ce9ab22ae1 notepad: use ref system for service 2025-10-16 23:01:48 -04:00
bbedward
72ad35e1f9 theme: don't depend on dms for gsettings theme mode 2025-10-16 22:58:49 -04:00
bbedward
c0d110cde0 controlcenter: fix trigger position via IPC 2025-10-16 22:03:02 -04:00
bbedward
b9d5deb2ae notifications: fix dnd tooltip & silence sounds on do not disturb 2025-10-16 21:30:45 -04:00
Nasser Alshammari
d4b13ef46b Dropbox icon workaround when DankBar is vertical (#466) 2025-10-16 21:22:15 -04:00
BB
748d9e342e Update translations/poexports/pt.json (POEditor.com) 2025-10-16 21:18:14 -04:00
purian23
f49312fc0e Update ReadMe 2025-10-16 21:17:54 -04:00
BB
e0d8bbb243 Update translations/poexports/pt.json (POEditor.com) 2025-10-16 19:59:55 -04:00
purian23
153f2a49f8 Update Copr Workflow 2025-10-16 19:59:29 -04:00
187 changed files with 30081 additions and 21038 deletions

View File

@@ -1,8 +1,10 @@
name: DMS Copr Stable Release
on:
release:
types: [published]
workflow_run:
workflows: ["Create Release from DMS"]
types: [completed]
branches: [master]
workflow_dispatch:
inputs:
version:
@@ -13,7 +15,8 @@ on:
jobs:
build-and-upload:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -24,16 +27,14 @@ jobs:
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"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version from workflow_run: $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//')
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/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"
@@ -49,7 +50,7 @@ jobs:
VERSION="${{ steps.version.outputs.version }}"
cd ~/rpmbuild/SOURCES
echo "📦 Downloading DMS release assets for v${VERSION}..."
echo "📦 Downloading DMS QML source for v${VERSION}..."
# Download DMS QML source
wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
@@ -57,21 +58,8 @@ jobs:
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"
echo "✅ Source downloaded"
echo "Note: dms-cli and dgop binaries will be downloaded during build based on target architecture"
ls -lh
- name: Generate stable spec file
@@ -95,12 +83,12 @@ jobs:
URL: https://github.com/AvengeMedia/DankMaterialShell
Source0: dms-qml.tar.gz
Source1: dms-distropkg-amd64.gz
Source2: dgop-linux-amd64.gz
BuildRequires: gzip
BuildRequires: wget
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
@@ -113,7 +101,6 @@ jobs:
Recommends: hyprpicker
Recommends: matugen
Recommends: wl-clipboard
Recommends: gammastep
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
@@ -150,10 +137,35 @@ jobs:
%prep
%setup -q -c -n dms-qml
gunzip -c %{SOURCE1} > %{_builddir}/dms-cli
# Download architecture-specific binaries during build
# This ensures the correct architecture is used for each build target
case "%{_arch}" in
x86_64)
ARCH_SUFFIX="amd64"
;;
aarch64)
ARCH_SUFFIX="arm64"
;;
*)
echo "Unsupported architecture: %{_arch}"
exit 1
;;
esac
# Download dms-cli for target architecture
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dms-cli for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
gunzip -c %{SOURCE2} > %{_builddir}/dgop
# Download dgop for target architecture
wget -O %{_builddir}/dgop.gz "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-${ARCH_SUFFIX}.gz" || {
echo "Failed to download dgop for architecture %{_arch}"
exit 1
}
gunzip -c %{_builddir}/dgop.gz > %{_builddir}/dgop
chmod +x %{_builddir}/dgop
%build
@@ -162,18 +174,59 @@ jobs:
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/
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/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
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms/*.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# Restart DMS for active users after upgrade
if [ "$1" -ge 2 ]; then
# Find all quickshell DMS processes (PID and username)
while read pid cmd; do
username=$(ps -o user= -p "$pid" 2>/dev/null)
[ "$username" = "root" ] && continue
[ -z "$username" ] && continue
# Get user's UID and validate session
user_uid=$(id -u "$username" 2>/dev/null)
[ -z "$user_uid" ] && continue
[ ! -d "/run/user/$user_uid" ] && continue
wayland_display=$(tr '\0' '\n' < /proc/$pid/environ 2>/dev/null | grep '^WAYLAND_DISPLAY=' | cut -d= -f2)
[ -z "$wayland_display" ] && continue
echo "Restarting DMS for user: $username"
# Run as user with full Wayland session environment
runuser -u "$username" -- /bin/sh -c "
export XDG_RUNTIME_DIR=/run/user/$user_uid
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$user_uid/bus
export WAYLAND_DISPLAY=$wayland_display
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:\$PATH
dms restart >/dev/null 2>&1
" 2>/dev/null || true
break
done < <(pgrep -a -f 'quickshell.*dms' 2>/dev/null)
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%{_datadir}/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms

View File

@@ -10,106 +10,181 @@ concurrency:
cancel-in-progress: false
jobs:
diff-and-trigger:
sync-translations:
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: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- 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
- name: Extract source strings from codebase
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 "::group::Extracting strings from QML files"
python3 translations/extract_translations.py
echo "::endgroup::"
# 2) Download exported content
curl -sS -L "$URL" -o /tmp/po_export.json
echo "::group::Checking for changes in en.json"
if [[ -f "translations/en.json" ]]; then
jq -S . "translations/en.json" > /tmp/en_new.json
if [[ -f "translations/en.json.orig" ]]; then
jq -S . "translations/en.json.orig" > /tmp/en_old.json
else
git show HEAD:translations/en.json > /tmp/en_old.json 2>/dev/null || echo "[]" > /tmp/en_old.json
jq -S . /tmp/en_old.json > /tmp/en_old.json.tmp && mv /tmp/en_old.json.tmp /tmp/en_old.json
fi
# 3) Normalize JSON (sorted keys) for stable diff
jq -S . /tmp/po_export.json > /tmp/po_export.norm.json
if diff -q /tmp/en_new.json /tmp/en_old.json >/dev/null 2>&1; then
echo "No changes in source strings"
echo "source_changed=false" >> "$GITHUB_OUTPUT"
else
echo "Detected changes in source strings"
echo "source_changed=true" >> "$GITHUB_OUTPUT"
# 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
echo "::group::Uploading source strings to POEditor"
RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/upload \
-F api_token="$API_TOKEN" \
-F id="$PROJECT_ID" \
-F updating="terms" \
-F file=@"translations/en.json")
STATUS=$(echo "$RESP" | jq -r '.response.status')
if [[ "$STATUS" != "success" ]]; then
echo "::warning::POEditor upload failed: $RESP"
else
TERMS_ADDED=$(echo "$RESP" | jq -r '.result.terms.added // 0')
TERMS_UPDATED=$(echo "$RESP" | jq -r '.result.terms.updated // 0')
TERMS_DELETED=$(echo "$RESP" | jq -r '.result.terms.deleted // 0')
echo "Terms added: $TERMS_ADDED, updated: $TERMS_UPDATED, deleted: $TERMS_DELETED"
fi
echo "::endgroup::"
fi
else
echo "{}" > /tmp/repo.norm.json
echo "::warning::translations/en.json not found"
echo "source_changed=false" >> "$GITHUB_OUTPUT"
fi
echo "::endgroup::"
id: extract
# 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] }}
- name: Commit and push source strings
if: steps.extract.outputs.source_changed == 'true'
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 }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add translations/en.json translations/template.json
git commit -m "i18n: update source strings from codebase"
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)"
if git push; then
echo "Successfully pushed source string updates"
exit 0
fi
echo "Attempt $attempt failed ($code) → $(cat /tmp/resp.txt)"
sleep $((attempt*3))
echo "Push attempt $attempt failed, pulling and retrying..."
git pull --rebase
sleep $((attempt*2))
done
echo "Webhook failed after retries." >&2
echo "Failed to push after retries" >&2
exit 1
- name: Export and update translations from POEditor
env:
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
run: |
set -euo pipefail
LANGUAGES=(
"ja:translations/poexports/ja.json"
"zh-Hans:translations/poexports/zh_CN.json"
"pt-br:translations/poexports/pt.json"
"tr:translations/poexports/tr.json"
)
ANY_CHANGED=false
for lang_pair in "${LANGUAGES[@]}"; do
IFS=':' read -r PO_LANG REPO_FILE <<< "$lang_pair"
echo "::group::Processing $PO_LANG"
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")
STATUS=$(echo "$RESP" | jq -r '.response.status')
if [[ "$STATUS" != "success" ]]; then
echo "POEditor export request failed for $PO_LANG: $RESP" >&2
continue
fi
URL=$(echo "$RESP" | jq -r '.result.url')
if [[ -z "$URL" || "$URL" == "null" ]]; then
echo "No export URL returned for $PO_LANG" >&2
continue
fi
curl -sS -L "$URL" -o "/tmp/po_export_${PO_LANG}.json"
jq -S . "/tmp/po_export_${PO_LANG}.json" > "/tmp/po_export_${PO_LANG}.norm.json"
if [[ -f "$REPO_FILE" ]]; then
jq -S . "$REPO_FILE" > "/tmp/repo_${PO_LANG}.norm.json" || echo "{}" > "/tmp/repo_${PO_LANG}.norm.json"
else
echo "{}" > "/tmp/repo_${PO_LANG}.norm.json"
fi
if diff -q "/tmp/po_export_${PO_LANG}.norm.json" "/tmp/repo_${PO_LANG}.norm.json" >/dev/null; then
echo "No changes for $PO_LANG"
else
echo "Detected changes for $PO_LANG"
mkdir -p "$(dirname "$REPO_FILE")"
cp "/tmp/po_export_${PO_LANG}.norm.json" "$REPO_FILE"
ANY_CHANGED=true
fi
echo "::endgroup::"
done
echo "any_changed=$ANY_CHANGED" >> "$GITHUB_OUTPUT"
id: export
- name: Commit and push translation updates
if: steps.export.outputs.any_changed == 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add translations/poexports/*.json
git commit -m "i18n: update translations"
for attempt in 1 2 3; do
if git push; then
echo "Successfully pushed translation updates"
exit 0
fi
echo "Push attempt $attempt failed, pulling and retrying..."
git pull --rebase
sleep $((attempt*2))
done
echo "Failed to push after retries" >&2
exit 1

View File

@@ -7,6 +7,7 @@ on:
permissions:
contents: write
actions: write
concurrency:
group: release-${{ github.event.client_payload.tag }}
@@ -48,9 +49,9 @@ jobs:
set -e
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' | head -50)
else
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' "${PREVIOUS_TAG}..${TAG}")
fi
cat > RELEASE_BODY.md << 'EOF'
@@ -216,4 +217,5 @@ jobs:
tag_name: ${{ env.TAG }}
files: _release_assets/**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -191,9 +191,9 @@ shell.qml # Main entry point (minimal orchestration)
Singleton {
id: root
property type value: defaultValue
function performAction() { /* implementation */ }
}
```
@@ -251,23 +251,23 @@ shell.qml # Main entry point (minimal orchestration)
// For regular components
Item {
id: root
property type name: value
signal customSignal(type param)
onSignal: { /* handler */ }
Component { /* children */ }
}
// For services (singletons)
Singleton {
id: root
property bool featureAvailable: false
property type currentValue: defaultValue
function performAction(param) { /* implementation */ }
}
```
@@ -305,7 +305,7 @@ shell.qml # Main entry point (minimal orchestration)
```qml
// In services - detect capabilities
property bool brightnessAvailable: false
// In modules - adapt UI accordingly
DankSlider {
visible: DisplayService.brightnessAvailable
@@ -335,7 +335,7 @@ shell.qml # Main entry point (minimal orchestration)
console.log("Info message") // General info
console.warn("Warning message") // Warnings
console.error("Error message") // Errors
// Include context in service operations
onExited: (exitCode) => {
if (exitCode !== 0) {

View File

@@ -67,7 +67,7 @@ Singleton {
}
function migrateFromUndefinedToV1(cache) {
console.log("CacheData: Migrating configuration from undefined to version 1")
console.info("CacheData: Migrating configuration from undefined to version 1")
}
function cleanupUnusedKeys() {
@@ -115,7 +115,7 @@ Singleton {
}
onLoadFailed: error => {
if (!isGreeterMode) {
console.log("CacheData: No cache file found, starting fresh")
console.info("CacheData: No cache file found, starting fresh")
}
}
}

View File

@@ -16,7 +16,7 @@ Singleton {
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"
@@ -43,7 +43,7 @@ Singleton {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
console.info(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
@@ -84,7 +84,7 @@ Singleton {
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
console.info(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
}
function _fallbackToEnglish() {

View File

@@ -49,6 +49,7 @@ Singleton {
property int nightModeEndMinute: 0
property real latitude: 0.0
property real longitude: 0.0
property bool nightModeUseIPLocation: false
property string nightModeLocationProvider: ""
property var pinnedApps: []
@@ -112,6 +113,7 @@ Singleton {
}
latitude = settings.latitude !== undefined ? settings.latitude : 0.0
longitude = settings.longitude !== undefined ? settings.longitude : 0.0
nightModeUseIPLocation = settings.nightModeUseIPLocation !== undefined ? settings.nightModeUseIPLocation : false
nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : ""
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0
@@ -171,6 +173,7 @@ Singleton {
"nightModeEndMinute": nightModeEndMinute,
"latitude": latitude,
"longitude": longitude,
"nightModeUseIPLocation": nightModeUseIPLocation,
"nightModeLocationProvider": nightModeLocationProvider,
"pinnedApps": pinnedApps,
"selectedGpuIndex": selectedGpuIndex,
@@ -193,7 +196,7 @@ Singleton {
}
function migrateFromUndefinedToV1(settings) {
console.log("SessionData: Migrating configuration from undefined to version 1")
console.info("SessionData: Migrating configuration from undefined to version 1")
if (typeof SettingsData !== "undefined") {
if (settings.acMonitorTimeout !== undefined) {
SettingsData.setAcMonitorTimeout(settings.acMonitorTimeout)
@@ -247,11 +250,11 @@ Singleton {
"monitorWallpapersDark", "doNotDisturb", "nightModeEnabled",
"nightModeTemperature", "nightModeAutoEnabled", "nightModeAutoMode",
"nightModeStartHour", "nightModeStartMinute", "nightModeEndHour",
"nightModeEndMinute", "latitude", "longitude", "nightModeLocationProvider",
"nightModeEndMinute", "latitude", "longitude", "nightModeUseIPLocation", "nightModeLocationProvider",
"pinnedApps", "selectedGpuIndex", "nvidiaGpuTempEnabled",
"nonNvidiaGpuTempEnabled", "enabledGpuPciIds", "wallpaperCyclingEnabled",
"wallpaperCyclingMode", "wallpaperCyclingInterval", "wallpaperCyclingTime",
"monitorCyclingSettings", "lastBrightnessDevice", "wallpaperTransition",
"monitorCyclingSettings", "lastBrightnessDevice", "launchPrefix", "wallpaperTransition",
"includedTransitions", "recentColors", "showThirdPartyPlugins", "configVersion"
]
@@ -536,6 +539,11 @@ Singleton {
saveSettings()
}
function setNightModeUseIPLocation(use) {
nightModeUseIPLocation = use
saveSettings()
}
function setLatitude(lat) {
console.log("SessionData: Setting latitude to", lat)
latitude = lat
@@ -699,7 +707,7 @@ Singleton {
running: false
onExited: exitCode => {
if (exitCode === 0) {
console.log("Copied default-session.json to session.json")
console.info("Copied default-session.json to session.json")
settingsFile.reload()
}
}

View File

@@ -25,10 +25,10 @@ Singleton {
enum AnimationSpeed {
None,
Shortest,
Short,
Medium,
Long
Long,
Custom
}
readonly property string defaultFontFamily: "Inter Variable"
@@ -64,6 +64,10 @@ Singleton {
property bool useFahrenheit: false
property bool nightModeEnabled: false
property int animationSpeed: SettingsData.AnimationSpeed.Short
property int customAnimationDuration: 500
property string wallpaperFillMode: "Fill"
property bool blurredWallpaperLayer: false
property bool blurWallpaperOnOverview: false
property bool showLauncherButton: true
property bool showWorkspaceSwitcher: true
@@ -99,6 +103,7 @@ Singleton {
property bool showWorkspaceIndex: false
property bool showWorkspacePadding: false
property bool workspaceScrolling: false
property bool showWorkspaceApps: false
property int maxWorkspaceIcons: 3
property bool workspacesPerMonitor: true
@@ -108,6 +113,7 @@ Singleton {
property bool focusedWindowCompactMode: false
property bool runningAppsCompactMode: true
property bool runningAppsCurrentWorkspace: false
property bool runningAppsGroupByApp: false
property string clockDateFormat: ""
property string lockDateFormat: ""
property int mediaSize: 1
@@ -185,6 +191,7 @@ Singleton {
property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property string launchPrefix: ""
property var brightnessDevicePins: ({})
property bool gtkThemingEnabled: false
property bool qtThemingEnabled: false
@@ -198,11 +205,15 @@ Singleton {
property real dockSpacing: 4
property real dockBottomGap: 0
property real dockIconSize: 40
property string dockIndicatorStyle: "circle"
property bool notificationOverlayEnabled: false
property bool dankBarAutoHide: false
property bool dankBarOpenOnOverview: false
property bool dankBarVisible: true
property int overviewRows: 2
property int overviewColumns: 5
property real overviewScale: 0.16
property real dankBarSpacing: 4
property real dankBarBottomGap: 0
property real dankBarInnerPadding: 4
@@ -238,6 +249,7 @@ Singleton {
property bool osdAlwaysShowValue: false
property bool powerActionConfirm: true
property string customPowerActionLock: ""
property string customPowerActionLogout: ""
property string customPowerActionSuspend: ""
property string customPowerActionHibernate: ""
@@ -249,6 +261,7 @@ Singleton {
property string updaterTerminalAdditionalParams: ""
property var screenPreferences: ({})
property var showOnLastDisplay: ({})
signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh
@@ -310,7 +323,7 @@ Singleton {
} else if (settings.themeIndex >= 0 && settings.themeIndex < themeNames.length) {
currentThemeName = themeNames[settings.themeIndex]
}
console.log("Auto-migrated theme from index", settings.themeIndex, "to", currentThemeName)
console.info("Auto-migrated theme from index", settings.themeIndex, "to", currentThemeName)
} else {
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue"
}
@@ -361,6 +374,7 @@ Singleton {
]
showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false
showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false
workspaceScrolling = settings.workspaceScrolling !== undefined ? settings.workspaceScrolling : false
showWorkspaceApps = settings.showWorkspaceApps !== undefined ? settings.showWorkspaceApps : false
maxWorkspaceIcons = settings.maxWorkspaceIcons !== undefined ? settings.maxWorkspaceIcons : 3
workspaceNameIcons = settings.workspaceNameIcons !== undefined ? settings.workspaceNameIcons : ({})
@@ -370,6 +384,7 @@ Singleton {
focusedWindowCompactMode = settings.focusedWindowCompactMode !== undefined ? settings.focusedWindowCompactMode : false
runningAppsCompactMode = settings.runningAppsCompactMode !== undefined ? settings.runningAppsCompactMode : true
runningAppsCurrentWorkspace = settings.runningAppsCurrentWorkspace !== undefined ? settings.runningAppsCurrentWorkspace : false
runningAppsGroupByApp = settings.runningAppsGroupByApp !== undefined ? settings.runningAppsGroupByApp : false
clockDateFormat = settings.clockDateFormat !== undefined ? settings.clockDateFormat : ""
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : ""
mediaSize = settings.mediaSize !== undefined ? settings.mediaSize : (settings.mediaCompactMode !== undefined ? (settings.mediaCompactMode ? 0 : 1) : 1)
@@ -440,6 +455,7 @@ Singleton {
dockSpacing = settings.dockSpacing !== undefined ? settings.dockSpacing : 4
dockBottomGap = settings.dockBottomGap !== undefined ? settings.dockBottomGap : 0
dockIconSize = settings.dockIconSize !== undefined ? settings.dockIconSize : 40
dockIndicatorStyle = settings.dockIndicatorStyle !== undefined ? settings.dockIndicatorStyle : "circle"
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12
notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false
dankBarAutoHide = settings.dankBarAutoHide !== undefined ? settings.dankBarAutoHide : (settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false)
@@ -452,6 +468,7 @@ Singleton {
notificationPopupPosition = settings.notificationPopupPosition !== undefined ? settings.notificationPopupPosition : SettingsData.Position.Top
osdAlwaysShowValue = settings.osdAlwaysShowValue !== undefined ? settings.osdAlwaysShowValue : false
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true
customPowerActionLock = settings.customPowerActionLock != undefined ? settings.customPowerActionLock : ""
customPowerActionLogout = settings.customPowerActionLogout != undefined ? settings.customPowerActionLogout : ""
customPowerActionSuspend = settings.customPowerActionSuspend != undefined ? settings.customPowerActionSuspend : ""
customPowerActionHibernate = settings.customPowerActionHibernate != undefined ? settings.customPowerActionHibernate : ""
@@ -480,7 +497,12 @@ Singleton {
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
showOnLastDisplay = settings.showOnLastDisplay !== undefined ? settings.showOnLastDisplay : ({})
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill"
blurredWallpaperLayer = settings.blurredWallpaperLayer !== undefined ? settings.blurredWallpaperLayer : false
blurWallpaperOnOverview = settings.blurWallpaperOnOverview !== undefined ? settings.blurWallpaperOnOverview : false
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
customAnimationDuration = settings.customAnimationDuration !== undefined ? settings.customAnimationDuration : 500
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
acLockTimeout = settings.acLockTimeout !== undefined ? settings.acLockTimeout : 0
acSuspendTimeout = settings.acSuspendTimeout !== undefined ? settings.acSuspendTimeout : 0
@@ -492,6 +514,7 @@ Singleton {
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
brightnessDevicePins = settings.brightnessDevicePins !== undefined ? settings.brightnessDevicePins : ({})
if (settings.configVersion === undefined) {
migrateFromUndefinedToV1(settings)
@@ -561,6 +584,7 @@ Singleton {
"controlCenterShowAudioIcon": controlCenterShowAudioIcon,
"controlCenterWidgets": controlCenterWidgets,
"showWorkspaceIndex": showWorkspaceIndex,
"workspaceScrolling": workspaceScrolling,
"showWorkspacePadding": showWorkspacePadding,
"showWorkspaceApps": showWorkspaceApps,
"maxWorkspaceIcons": maxWorkspaceIcons,
@@ -571,6 +595,7 @@ Singleton {
"focusedWindowCompactMode": focusedWindowCompactMode,
"runningAppsCompactMode": runningAppsCompactMode,
"runningAppsCurrentWorkspace": runningAppsCurrentWorkspace,
"runningAppsGroupByApp": runningAppsGroupByApp,
"clockDateFormat": clockDateFormat,
"lockDateFormat": lockDateFormat,
"mediaSize": mediaSize,
@@ -616,6 +641,7 @@ Singleton {
"dockSpacing": dockSpacing,
"dockBottomGap": dockBottomGap,
"dockIconSize": dockIconSize,
"dockIndicatorStyle": dockIndicatorStyle,
"cornerRadius": cornerRadius,
"notificationOverlayEnabled": notificationOverlayEnabled,
"dankBarAutoHide": dankBarAutoHide,
@@ -640,12 +666,16 @@ Singleton {
"hideBrightnessSlider": hideBrightnessSlider,
"widgetBackgroundColor": widgetBackgroundColor,
"surfaceBase": surfaceBase,
"wallpaperFillMode": wallpaperFillMode,
"blurredWallpaperLayer": blurredWallpaperLayer,
"blurWallpaperOnOverview": blurWallpaperOnOverview,
"notificationTimeoutLow": notificationTimeoutLow,
"notificationTimeoutNormal": notificationTimeoutNormal,
"notificationTimeoutCritical": notificationTimeoutCritical,
"notificationPopupPosition": notificationPopupPosition,
"osdAlwaysShowValue": osdAlwaysShowValue,
"powerActionConfirm": powerActionConfirm,
"customPowerActionLock": customPowerActionLock,
"customPowerActionLogout": customPowerActionLogout,
"customPowerActionSuspend": customPowerActionSuspend,
"customPowerActionHibernate": customPowerActionHibernate,
@@ -655,7 +685,9 @@ Singleton {
"updaterCustomCommand": updaterCustomCommand,
"updaterTerminalAdditionalParams": updaterTerminalAdditionalParams,
"screenPreferences": screenPreferences,
"showOnLastDisplay": showOnLastDisplay,
"animationSpeed": animationSpeed,
"customAnimationDuration": customAnimationDuration,
"acMonitorTimeout": acMonitorTimeout,
"acLockTimeout": acLockTimeout,
"acSuspendTimeout": acSuspendTimeout,
@@ -667,6 +699,7 @@ Singleton {
"lockBeforeSuspend": lockBeforeSuspend,
"loginctlLockIntegration": loginctlLockIntegration,
"launchPrefix": launchPrefix,
"brightnessDevicePins": brightnessDevicePins,
"configVersion": settingsConfigVersion
}, null, 2))
}
@@ -678,7 +711,7 @@ Singleton {
}
function migrateFromUndefinedToV1(settings) {
console.log("SettingsData: Migrating configuration from undefined to version 1")
console.info("SettingsData: Migrating configuration from undefined to version 1")
}
function cleanupUnusedKeys() {
@@ -692,10 +725,10 @@ Singleton {
"selectedGpuIndex", "enabledGpuPciIds", "showSystemTray", "showClock",
"showNotificationButton", "showBattery", "showControlCenterButton",
"controlCenterShowNetworkIcon", "controlCenterShowBluetoothIcon", "controlCenterShowAudioIcon",
"controlCenterWidgets", "showWorkspaceIndex", "showWorkspacePadding", "showWorkspaceApps",
"controlCenterWidgets", "showWorkspaceIndex", "workspaceScrolling", "showWorkspacePadding", "showWorkspaceApps",
"maxWorkspaceIcons", "workspacesPerMonitor", "workspaceNameIcons", "waveProgressEnabled",
"clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode",
"runningAppsCurrentWorkspace", "clockDateFormat", "lockDateFormat", "mediaSize",
"runningAppsCurrentWorkspace", "runningAppsGroupByApp", "clockDateFormat", "lockDateFormat", "mediaSize",
"dankBarLeftWidgets", "dankBarCenterWidgets", "dankBarRightWidgets",
"appLauncherViewMode", "spotlightModalViewMode", "sortAppsAlphabetically",
"networkPreference", "iconTheme", "launcherLogoMode", "launcherLogoCustomPath",
@@ -706,23 +739,23 @@ Singleton {
"notepadTransparencyOverride", "notepadLastCustomTransparency", "soundsEnabled",
"useSystemSoundTheme", "soundNewNotification", "soundVolumeChanged", "soundPluggedIn", "gtkThemingEnabled",
"qtThemingEnabled", "syncModeWithPortal", "showDock", "dockAutoHide", "dockGroupByApp",
"dockOpenOnOverview", "dockPosition", "dockSpacing", "dockBottomGap", "dockIconSize",
"dockOpenOnOverview", "dockPosition", "dockSpacing", "dockBottomGap", "dockIconSize", "dockIndicatorStyle",
"cornerRadius", "notificationOverlayEnabled", "dankBarAutoHide",
"dankBarOpenOnOverview", "dankBarVisible", "dankBarSpacing", "dankBarBottomGap",
"dankBarInnerPadding", "dankBarSquareCorners", "dankBarNoBackground",
"dankBarGothCornersEnabled", "dankBarBorderEnabled", "dankBarBorderColor",
"dankBarBorderOpacity", "dankBarBorderThickness", "popupGapsAuto", "popupGapsManual",
"dankBarPosition", "lockScreenShowPowerActions", "enableFprint", "maxFprintTries",
"hideBrightnessSlider", "widgetBackgroundColor", "surfaceBase",
"notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"hideBrightnessSlider", "widgetBackgroundColor", "surfaceBase", "wallpaperFillMode",
"blurredWallpaperLayer", "blurWallpaperOnOverview", "notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"notificationPopupPosition", "osdAlwaysShowValue", "powerActionConfirm",
"customPowerActionLogout", "customPowerActionSuspend", "customPowerActionHibernate",
"customPowerActionReboot", "customPowerActionPowerOff",
"customPowerActionLock", "customPowerActionLogout", "customPowerActionSuspend",
"customPowerActionHibernate", "customPowerActionReboot", "customPowerActionPowerOff",
"updaterUseCustomCommand", "updaterCustomCommand", "updaterTerminalAdditionalParams",
"screenPreferences", "animationSpeed", "acMonitorTimeout", "acLockTimeout",
"screenPreferences", "showOnLastDisplay", "animationSpeed", "customAnimationDuration", "acMonitorTimeout", "acLockTimeout",
"acSuspendTimeout", "acHibernateTimeout", "batteryMonitorTimeout", "batteryLockTimeout",
"batterySuspendTimeout", "batteryHibernateTimeout", "lockBeforeSuspend",
"loginctlLockIntegration", "launchPrefix", "configVersion"
"loginctlLockIntegration", "launchPrefix", "brightnessDevicePins", "configVersion"
]
try {
@@ -919,7 +952,11 @@ Singleton {
if (prefs.includes("all")) {
return Quickshell.screens
}
return Quickshell.screens.filter(screen => prefs.includes(screen.name))
var filtered = Quickshell.screens.filter(screen => prefs.includes(screen.name))
if (filtered.length === 0 && showOnLastDisplay && showOnLastDisplay[componentId] && Quickshell.screens.length === 1) {
return Quickshell.screens
}
return filtered
}
function sendTestNotifications() {
@@ -1019,6 +1056,7 @@ Singleton {
function setCornerRadius(radius) {
cornerRadius = radius
saveSettings()
NiriService.generateNiriLayoutConfig()
}
function setClockFormat(use24Hour) {
@@ -1046,6 +1084,26 @@ Singleton {
saveSettings()
}
function setCustomAnimationDuration(duration) {
customAnimationDuration = duration
saveSettings()
}
function setWallpaperFillMode(mode) {
wallpaperFillMode = mode
saveSettings()
}
function setBlurredWallpaperLayer(enabled) {
blurredWallpaperLayer = enabled
saveSettings()
}
function setBlurWallpaperOnOverview(enabled) {
blurWallpaperOnOverview = enabled
saveSettings()
}
function setShowLauncherButton(enabled) {
showLauncherButton = enabled
saveSettings()
@@ -1156,6 +1214,11 @@ Singleton {
saveSettings()
}
function setWorkspaceScrolling(enabled) {
workspaceScrolling = enabled
saveSettings()
}
function setShowWorkspacePadding(enabled) {
showWorkspacePadding = enabled
saveSettings()
@@ -1221,6 +1284,11 @@ Singleton {
saveSettings()
}
function setRunningAppsGroupByApp(enabled) {
runningAppsGroupByApp = enabled
saveSettings()
}
function setClockDateFormat(format) {
clockDateFormat = format || ""
saveSettings()
@@ -1569,6 +1637,11 @@ Singleton {
saveSettings()
}
function setDockIndicatorStyle(style) {
dockIndicatorStyle = style
saveSettings()
}
function setNotificationOverlayEnabled(enabled) {
notificationOverlayEnabled = enabled
saveSettings()
@@ -1713,6 +1786,11 @@ Singleton {
saveSettings();
}
function setCustomPowerActionLock(command) {
customPowerActionLock = command;
saveSettings();
}
function setCustomPowerActionLogout(command) {
customPowerActionLogout = command;
saveSettings();
@@ -1758,6 +1836,16 @@ Singleton {
saveSettings()
}
function setShowOnLastDisplay(prefs) {
showOnLastDisplay = prefs
saveSettings()
}
function setBrightnessDevicePins(pins) {
brightnessDevicePins = pins
saveSettings()
}
function getPluginSetting(pluginId, key, defaultValue) {
if (!pluginSettings[pluginId]) {
return defaultValue
@@ -1952,7 +2040,7 @@ Singleton {
running: false
onExited: exitCode => {
if (exitCode === 0) {
console.log("Copied default-settings.json to settings.json")
console.info("Copied default-settings.json to settings.json")
settingsFile.reload()
} else {
applyStoredTheme()

View File

@@ -61,7 +61,7 @@ Singleton {
}
readonly property string rawWallpaperPath: {
if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens
@@ -92,7 +92,7 @@ Singleton {
}
if (colorsFileLoadFailed && currentTheme === dynamic && wallpaperPath) {
console.log("Theme: Matugen now available, regenerating colors for dynamic theme")
console.info("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"])
@@ -195,14 +195,16 @@ Singleton {
}
readonly property var availableMatugenSchemes: [
({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": "Balanced palette with focused accents (default)." }),
({ "value": "scheme-content", "label": "Content", "description": "Derives colors that closely match the underlying image." }),
({ "value": "scheme-expressive", "label": "Expressive", "description": "Vibrant palette with playful saturation." }),
({ "value": "scheme-fidelity", "label": "Fidelity", "description": "High-fidelity palette that preserves source hues." }),
({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": "Colorful mix of bright contrasting accents." }),
({ "value": "scheme-monochrome", "label": "Monochrome", "description": "Minimal palette built around a single hue." }),
({ "value": "scheme-neutral", "label": "Neutral", "description": "Muted palette with subdued, calming tones." }),
({ "value": "scheme-rainbow", "label": "Rainbow", "description": "Diverse palette spanning the full spectrum." })
({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": I18n.tr("Balanced palette with focused accents (default).") }),
({ "value": "scheme-vibrant-spot", "label": "Vibrant Spot", "description": I18n.tr("Lively palette with saturated accents.") }),
({ "value": "scheme-dynamic-contrast", "label": "Dynamic Contrast", "description": I18n.tr("High-contrast palette for strong visual distinction.") }),
({ "value": "scheme-content", "label": "Content", "description": I18n.tr("Derives colors that closely match the underlying image.") }),
({ "value": "scheme-expressive", "label": "Expressive", "description": I18n.tr("Vibrant palette with playful saturation.") }),
({ "value": "scheme-fidelity", "label": "Fidelity", "description": I18n.tr("High-fidelity palette that preserves source hues.") }),
({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": I18n.tr("Colorful mix of bright contrasting accents.") }),
({ "value": "scheme-monochrome", "label": "Monochrome", "description": I18n.tr("Minimal palette built around a single hue.") }),
({ "value": "scheme-neutral", "label": "Neutral", "description": I18n.tr("Muted palette with subdued, calming tones.") }),
({ "value": "scheme-rainbow", "label": "Rainbow", "description": I18n.tr("Diverse palette spanning the full spectrum.") })
]
function getMatugenScheme(value) {
@@ -313,6 +315,61 @@ Singleton {
property int standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart
readonly property var expressiveCurves: {
"emphasized": [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1],
"emphasizedAccel": [0.3, 0, 0.8, 0.15, 1, 1],
"emphasizedDecel": [0.05, 0.7, 0.1, 1, 1, 1],
"standard": [0.2, 0, 0, 1, 1, 1],
"standardAccel": [0.3, 0, 1, 1, 1, 1],
"standardDecel": [0, 0, 0, 1, 1, 1],
"expressiveFastSpatial": [0.42, 1.67, 0.21, 0.9, 1, 1],
"expressiveDefaultSpatial": [0.38, 1.21, 0.22, 1, 1, 1],
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
}
readonly property var animationPresetDurations: {
"none": 0,
"short": 250,
"medium": 500,
"long": 750
}
readonly property int currentAnimationBaseDuration: {
if (typeof SettingsData === "undefined") return 500
if (SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) {
return SettingsData.customAnimationDuration
}
const presetMap = [0, 250, 500, 750]
return presetMap[SettingsData.animationSpeed] !== undefined ? presetMap[SettingsData.animationSpeed] : 500
}
readonly property var expressiveDurations: {
if (typeof SettingsData === "undefined") {
return {
"fast": 200,
"normal": 400,
"large": 600,
"extraLarge": 1000,
"expressiveFastSpatial": 350,
"expressiveDefaultSpatial": 500,
"expressiveEffects": 200
}
}
const baseDuration = currentAnimationBaseDuration
return {
"fast": baseDuration * 0.4,
"normal": baseDuration * 0.8,
"large": baseDuration * 1.2,
"extraLarge": baseDuration * 2.0,
"expressiveFastSpatial": baseDuration * 0.7,
"expressiveDefaultSpatial": baseDuration,
"expressiveEffects": baseDuration * 0.4
}
}
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
property real spacingXS: 4
property real spacingS: 8
@@ -384,7 +441,10 @@ Singleton {
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
SessionData.setLightMode(light)
if (!isGreeterMode) {
PortalService.setLightMode(light)
// Skip with matugen becuase, our script runner will do it.
if (!matugenAvailable) {
PortalService.setLightMode(light)
}
generateSystemThemesFromCurrentTheme()
}
}
@@ -641,7 +701,7 @@ Singleton {
return
}
console.log("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
NiriService.suppressNextToast()
@@ -662,15 +722,16 @@ Singleton {
Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`])
workerRunning = true
const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false"
if (rawWallpaperPath.startsWith("we:")) {
console.log("Theme: Starting matugen worker (WE wallpaper)")
systemThemeGenerator.command = [
"sh", "-c",
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run`
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' --run`
]
} else {
console.log("Theme: Starting matugen worker")
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"]
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"]
}
systemThemeGenerator.running = true
}
@@ -761,6 +822,21 @@ Singleton {
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
function getFillMode(modeName) {
switch(modeName) {
case "Stretch": return Image.Stretch
case "Fit":
case "PreserveAspectFit": return Image.PreserveAspectFit
case "Fill":
case "PreserveAspectCrop": return Image.PreserveAspectCrop
case "Tile": return Image.Tile
case "TileVertically": return Image.TileVertically
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad
default: return Image.PreserveAspectCrop
}
}
function snap(value, dpr) {
const s = dpr || 1
return Math.round(value * s) / s
@@ -832,7 +908,7 @@ Singleton {
workerRunning = false
if (exitCode === 0) {
console.log("Theme: Matugen worker completed successfully")
console.info("Theme: Matugen worker completed successfully")
if (currentTheme === dynamic) {
console.log("Theme: Reloading dynamic colors file")
dynamicColorsFileView.reload()
@@ -907,7 +983,7 @@ Singleton {
onLoaded: {
if (currentTheme === dynamic) {
console.log("Theme: Dynamic colors file loaded successfully")
console.info("Theme: Dynamic colors file loaded successfully")
colorsFileLoadFailed = false
parseAndLoadColors()
}
@@ -921,7 +997,7 @@ Singleton {
onLoadFailed: function (error) {
if (currentTheme === dynamic) {
console.log("Theme: Dynamic colors file load failed, marking for regeneration")
console.warn("Theme: Dynamic colors file load failed, marking for regeneration")
colorsFileLoadFailed = true
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!isGreeterMode && matugenAvailable && wallpaperPath) {

View File

@@ -1,29 +1,11 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
Item {
Scope {
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
}
}
GreeterSurface {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,8 @@ Item {
required property var dankDashPopoutLoader
required property var notepadSlideoutVariants
required property var hyprKeybindsModalLoader
required property var dankBarLoader
required property var hyprlandOverviewLoader
IpcHandler {
function open() {
@@ -76,9 +78,8 @@ Item {
IpcHandler {
function open(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.open()
if (root.dankBarLoader.item) {
root.dankBarLoader.item.triggerControlCenterOnFocusedScreen()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
@@ -93,9 +94,8 @@ Item {
}
function toggle(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.toggle()
if (root.dankBarLoader.item) {
root.dankBarLoader.item.triggerControlCenterOnFocusedScreen()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
@@ -348,6 +348,41 @@ Item {
return "HYPR_KEYBINDS_TOGGLE_FAILED"
}
function toggleOverview(): string {
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
return root.hyprlandOverviewLoader.item.overviewOpen ? "OVERVIEW_OPEN_SUCCESS" : "OVERVIEW_CLOSE_SUCCESS"
}
function closeOverview(): string {
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprlandOverviewLoader.item.overviewOpen = false
return "OVERVIEW_CLOSE_SUCCESS"
}
function openOverview(): string {
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprlandOverviewLoader.item.overviewOpen = true
return "OVERVIEW_OPEN_SUCCESS"
}
target: "hypr"
}
IpcHandler {
function wallpaper(): string {
if (root.dankBarLoader.item && root.dankBarLoader.item.triggerWallpaperBrowserOnFocusedScreen()) {
return "SUCCESS: Toggled wallpaper browser"
}
return "ERROR: Failed to toggle wallpaper browser"
}
target: "dankdash"
}
}

View File

@@ -0,0 +1,362 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property string deviceName: ""
property string deviceAddress: ""
property string requestType: ""
property string token: ""
property int passkey: 0
property string pinInput: ""
property string passkeyInput: ""
function show(pairingData) {
token = pairingData.token || ""
deviceName = pairingData.deviceName || ""
deviceAddress = pairingData.deviceAddr || ""
requestType = pairingData.requestType || ""
passkey = pairingData.passkey || 0
pinInput = ""
passkeyInput = ""
open()
Qt.callLater(() => {
if (contentLoader.item) {
if (requestType === "pin" && contentLoader.item.pinInputField) {
contentLoader.item.pinInputField.forceActiveFocus()
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
contentLoader.item.passkeyInputField.forceActiveFocus()
}
}
})
}
shouldBeVisible: false
width: 420
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240
onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) {
pinInput = ""
passkeyInput = ""
}
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item) {
if (requestType === "pin" && contentLoader.item.pinInputField) {
contentLoader.item.pinInputField.forceActiveFocus()
} else if (requestType === "passkey" && contentLoader.item.passkeyInputField) {
contentLoader.item.passkeyInputField.forceActiveFocus()
}
}
})
}
onBackgroundClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
content: Component {
FocusScope {
id: pairingContent
property alias pinInputField: pinInputField
property alias passkeyInputField: passkeyInputField
anchors.fill: parent
focus: true
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
event.accepted = true
}
Column {
id: mainColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
spacing: requestType === "pin" || requestType === "passkey" ? Theme.spacingM : Theme.spacingS
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Pair Bluetooth Device")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: {
if (requestType === "confirm")
return I18n.tr("Confirm passkey for ") + deviceName
if (requestType === "authorize")
return I18n.tr("Authorize pairing with ") + deviceName
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
if (requestType === "pin")
return I18n.tr("Enter PIN for ") + deviceName
if (requestType === "passkey")
return I18n.tr("Enter passkey for ") + deviceName
return deviceName
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width - 40
elide: Text.ElideRight
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: pinInputField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: pinInputField.activeFocus ? 2 : 1
visible: requestType === "pin"
MouseArea {
anchors.fill: parent
onClicked: () => {
pinInputField.forceActiveFocus()
}
}
DankTextField {
id: pinInputField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: pinInput
placeholderText: I18n.tr("Enter PIN")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
pinInput = text
}
onAccepted: () => {
submitPairing()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: passkeyInputField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: passkeyInputField.activeFocus ? 2 : 1
visible: requestType === "passkey"
MouseArea {
anchors.fill: parent
onClicked: () => {
passkeyInputField.forceActiveFocus()
}
}
DankTextField {
id: passkeyInputField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: passkeyInput
placeholderText: I18n.tr("Enter 6-digit passkey")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
passkeyInput = text
}
onAccepted: () => {
submitPairing()
}
}
}
Rectangle {
width: parent.width
height: 56
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
visible: requestType === "confirm"
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Passkey:")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: String(passkey).padStart(6, "0")
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Item {
width: parent.width
height: 36
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
}
}
Rectangle {
width: Math.max(80, pairText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: pairArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: {
if (requestType === "pin")
return pinInput.length > 0
if (requestType === "passkey")
return passkeyInput.length === 6
return true
}
opacity: enabled ? 1 : 0.5
StyledText {
id: pairText
anchors.centerIn: parent
text: {
if (requestType === "confirm")
return I18n.tr("Confirm")
if (requestType === "authorize" || requestType.startsWith("authorize-service"))
return I18n.tr("Authorize")
return I18n.tr("Pair")
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: pairArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
submitPairing()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
DankActionButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
close()
pinInput = ""
passkeyInput = ""
}
}
}
}
function submitPairing() {
const secrets = {}
if (requestType === "pin") {
secrets["pin"] = pinInput
} else if (requestType === "passkey") {
secrets["passkey"] = passkeyInput
} else if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service")) {
secrets["decision"] = "yes"
}
DMSService.bluetoothSubmitPairing(token, secrets, true, response => {
if (response.error) {
ToastService.showError(I18n.tr("Pairing failed"), response.error)
}
})
close()
pinInput = ""
passkeyInput = ""
}
}

View File

@@ -84,7 +84,7 @@ Item {
id: clipboardListView
anchors.fill: parent
model: filteredModel
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
spacing: Theme.spacingXS
interactive: true
@@ -94,7 +94,7 @@ Item {
boundsMovement: Flickable.FollowBoundsBehavior
pressDelay: 0
flickableDirection: Flickable.VerticalFlick
function ensureVisible(index) {
if (index < 0 || index >= count) {
return
@@ -108,13 +108,13 @@ Item {
contentY = itemBottom - height
}
}
onCurrentIndexChanged: {
if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) {
ensureVisible(currentIndex)
}
}
StyledText {
text: I18n.tr("No clipboard entries found")
anchors.centerIn: parent
@@ -122,11 +122,11 @@ Item {
color: Theme.surfaceVariantText
visible: filteredModel.count === 0
}
delegate: ClipboardEntry {
required property int index
required property var model
width: clipboardListView.width
height: ClipboardConstants.itemHeight
entryData: model.entry

View File

@@ -35,8 +35,11 @@ PanelWindow {
property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true
property string animationType: "scale"
property int animationDuration: Theme.shortDuration
property var animationEasing: Theme.emphasizedEasing
property int animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial
property real animationScaleCollapsed: 0.96
property real animationOffset: Theme.spacingL
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium
property real borderWidth: 1
@@ -104,7 +107,7 @@ PanelWindow {
Timer {
id: closeTimer
interval: animationDuration + 100
interval: animationDuration + 120
onTriggered: {
visible = false
}
@@ -139,7 +142,8 @@ PanelWindow {
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
@@ -176,23 +180,67 @@ PanelWindow {
border.width: root.borderWidth
clip: false
layer.enabled: true
layer.smooth: true
opacity: root.shouldBeVisible ? 1 : 0
transform: root.animationType === "slide" ? slideTransform : null
transform: [scaleTransform, motionTransform]
Scale {
id: scaleTransform
origin.x: contentContainer.width / 2
origin.y: contentContainer.height / 2
xScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
yScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
Behavior on xScale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on yScale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
Translate {
id: slideTransform
id: motionTransform
readonly property real rawX: root.shouldBeVisible ? 0 : 15
readonly property real rawY: root.shouldBeVisible ? 0 : -30
readonly property bool slide: root.animationType === "slide"
readonly property real hiddenX: slide ? 15 : 0
readonly property real hiddenY: slide ? -30 : root.animationOffset
x: Theme.snap(rawX, root.dpr)
y: Theme.snap(rawY, root.dpr)
x: Theme.snap(root.shouldBeVisible ? 0 : hiddenX, root.dpr)
y: Theme.snap(root.shouldBeVisible ? 0 : hiddenY, root.dpr)
Behavior on x {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on y {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
Behavior on opacity {
NumberAnimation {
duration: animationDuration
easing.type: animationEasing
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}

View File

@@ -111,18 +111,18 @@ DankModal {
if (!normalizedPath.startsWith("file://")) {
normalizedPath = "file://" + filePath
}
// Check if file exists by looking through the folder model
var exists = false
var fileName = filePath.split('/').pop()
for (var i = 0; i < folderModel.count; i++) {
if (folderModel.get(i, "fileName") === fileName && !folderModel.get(i, "fileIsDir")) {
exists = true
break
}
}
if (exists) {
pendingFilePath = normalizedPath
showOverwriteConfirmation = true
@@ -139,7 +139,7 @@ DankModal {
Component.onCompleted: {
currentPath = getLastPath()
}
property var steamPaths: [
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960",
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960",
@@ -147,17 +147,17 @@ DankModal {
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960"
]
property int currentPathIndex: 0
function discoverWallpaperEngine() {
currentPathIndex = 0
checkNextPath()
}
function checkNextPath() {
if (currentPathIndex >= steamPaths.length) {
return
}
const wePath = steamPaths[currentPathIndex]
const cleanPath = wePath.replace(/^file:\/\//, '')
weDiscoveryProcess.command = ["test", "-d", cleanPath]
@@ -451,13 +451,13 @@ DankModal {
executeKeyboardSelection(targetIndex)
}
}
Process {
id: weDiscoveryProcess
property string wePath: ""
running: false
onExited: exitCode => {
if (exitCode === 0) {
fileBrowserModal.weAvailable = true
@@ -532,7 +532,7 @@ DankModal {
}
}
}
DankActionButton {
circular: false
iconName: "info"
@@ -875,26 +875,26 @@ DankModal {
id: overwriteDialog
anchors.fill: parent
visible: showOverwriteConfirmation
Keys.onEscapePressed: {
showOverwriteConfirmation = false
pendingFilePath = ""
}
Keys.onReturnPressed: {
showOverwriteConfirmation = false
fileSelected(pendingFilePath)
pendingFilePath = ""
Qt.callLater(() => fileBrowserModal.close())
}
focus: showOverwriteConfirmation
Rectangle {
anchors.fill: parent
color: Theme.shadowStrong
opacity: 0.8
MouseArea {
anchors.fill: parent
onClicked: {
@@ -903,7 +903,7 @@ DankModal {
}
}
}
StyledRect {
anchors.centerIn: parent
width: 400
@@ -912,12 +912,12 @@ DankModal {
radius: Theme.cornerRadius
border.color: Theme.outlineMedium
border.width: 1
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingM
StyledText {
text: I18n.tr("File Already Exists")
font.pixelSize: Theme.fontSizeLarge
@@ -925,7 +925,7 @@ DankModal {
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("A file with this name already exists. Do you want to overwrite it?")
font.pixelSize: Theme.fontSizeMedium
@@ -934,11 +934,11 @@ DankModal {
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
StyledRect {
width: 80
height: 36
@@ -946,7 +946,7 @@ DankModal {
color: cancelArea.containsMouse ? Theme.surfaceVariantHover : Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
StyledText {
anchors.centerIn: parent
text: I18n.tr("Cancel")
@@ -954,7 +954,7 @@ DankModal {
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
@@ -966,13 +966,13 @@ DankModal {
}
}
}
StyledRect {
width: 90
height: 36
radius: Theme.cornerRadius
color: overwriteArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText {
anchors.centerIn: parent
text: I18n.tr("Overwrite")
@@ -980,7 +980,7 @@ DankModal {
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: overwriteArea
anchors.fill: parent

View File

@@ -0,0 +1,162 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property bool networkWiredInfoModalVisible: false
property string networkID: ""
property var networkData: null
function showNetworkInfo(id, data) {
networkID = id
networkData = data
networkWiredInfoModalVisible = true
open()
NetworkService.fetchWiredNetworkInfo(data.uuid)
}
function hideDialog() {
networkWiredInfoModalVisible = false
close()
networkID = ""
networkData = null
}
visible: networkWiredInfoModalVisible
width: 600
height: 500
enableShadow: true
onBackgroundClicked: hideDialog()
onVisibleChanged: {
if (!visible) {
networkID = ""
networkData = null
}
}
content: Component {
Item {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Network Information")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: `Details for "${networkID}"`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.hideDialog()
}
}
Rectangle {
id: detailsRect
width: parent.width
height: parent.height - 140
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: Theme.outlineStrong
border.width: 1
clip: true
DankFlickable {
anchors.fill: parent
anchors.margins: Theme.spacingM
contentHeight: detailsText.contentHeight
StyledText {
id: detailsText
width: parent.width
text: NetworkService.networkWiredInfoDetails && NetworkService.networkWiredInfoDetails.replace(/\\n/g, '\n') || "No information available"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
wrapMode: Text.WordWrap
}
}
}
Item {
width: parent.width
height: 40
Rectangle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText {
id: closeText
anchors.centerIn: parent
text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.hideDialog()
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}

View File

@@ -20,7 +20,7 @@ DankModal {
if (modalKeyboardController && notificationListRef) {
modalKeyboardController.listView = notificationListRef
modalKeyboardController.rebuildFlatNavigation()
Qt.callLater(() => {
modalKeyboardController.keyboardNavigationActive = true
modalKeyboardController.selectedFlatIndex = 0

View File

@@ -391,6 +391,38 @@ Item {
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard lock procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customLockCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myLock.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionLock) {
text = SettingsData.customPowerActionLock;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionLock(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS

View File

@@ -2,12 +2,11 @@ import QtQuick
import qs.Common
import qs.Modules.Settings
FocusScope {
Item {
id: root
property int currentIndex: 0
property var parentModal: null
focus: true
Rectangle {
anchors.fill: parent
@@ -23,7 +22,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 0
visible: active
asynchronous: true
sourceComponent: Component {
PersonalizationTab {
@@ -40,7 +38,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 1
visible: active
asynchronous: true
sourceComponent: TimeWeatherTab {
}
@@ -53,7 +50,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 2
visible: active
asynchronous: true
sourceComponent: DankBarTab {
parentModal: root.parentModal
@@ -67,7 +63,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 3
visible: active
asynchronous: true
sourceComponent: WidgetTweaksTab {
}
@@ -80,7 +75,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 4
visible: active
asynchronous: true
sourceComponent: Component {
DockTab {
@@ -96,7 +90,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 5
visible: active
asynchronous: true
sourceComponent: DisplaysTab {
}
@@ -109,7 +102,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 6
visible: active
asynchronous: true
sourceComponent: LauncherTab {
}
@@ -122,7 +114,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 7
visible: active
asynchronous: true
sourceComponent: ThemeColorsTab {
}
@@ -135,7 +126,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 8
visible: active
asynchronous: true
sourceComponent: PowerSettings {
}
@@ -148,7 +138,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 9
visible: active
asynchronous: true
sourceComponent: PluginsTab {
parentModal: root.parentModal
@@ -162,7 +151,6 @@ FocusScope {
anchors.fill: parent
active: root.currentIndex === 10
visible: active
asynchronous: true
sourceComponent: AboutTab {
}

View File

@@ -13,6 +13,7 @@ DankModal {
property Component settingsContent
property alias profileBrowser: profileBrowser
property int currentTabIndex: 0
signal closingModal()
@@ -40,6 +41,25 @@ DankModal {
return hide();
}
content: settingsContent
onOpened: () => {
Qt.callLater(() => modalFocusScope.forceActiveFocus())
}
modalFocusScope.Keys.onPressed: event => {
const tabCount = 11
if (event.key === Qt.Key_Down) {
currentTabIndex = (currentTabIndex + 1) % tabCount
event.accepted = true
} else if (event.key === Qt.Key_Up) {
currentTabIndex = (currentTabIndex - 1 + tabCount) % tabCount
event.accepted = true
} else if (event.key === Qt.Key_Tab && !event.modifiers) {
currentTabIndex = (currentTabIndex + 1) % tabCount
event.accepted = true
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && event.modifiers & Qt.ShiftModifier)) {
currentTabIndex = (currentTabIndex - 1 + tabCount) % tabCount
event.accepted = true
}
}
IpcHandler {
function open(): string {
@@ -82,6 +102,7 @@ DankModal {
browserTitle: "Select Profile Image"
browserIcon: "person"
browserType: "profile"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: (path) => {
PortalService.setProfileImage(path);
@@ -100,6 +121,7 @@ DankModal {
browserTitle: "Select Wallpaper"
browserIcon: "wallpaper"
browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: (path) => {
SessionData.setWallpaper(path);
@@ -111,9 +133,9 @@ DankModal {
}
settingsContent: Component {
FocusScope {
Item {
id: rootScope
anchors.fill: parent
focus: true
Column {
anchors.fill: parent
@@ -172,7 +194,10 @@ DankModal {
id: sidebar
parentModal: settingsModal
onCurrentIndexChanged: content.currentIndex = currentIndex
currentIndex: settingsModal.currentTabIndex
onCurrentIndexChanged: {
settingsModal.currentTabIndex = currentIndex
}
}
SettingsContent {
@@ -181,7 +206,7 @@ DankModal {
width: parent.width - sidebar.width
height: parent.height
parentModal: settingsModal
currentIndex: sidebar.currentIndex
currentIndex: settingsModal.currentTabIndex
}
}

View File

@@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Modals.Settings
@@ -43,6 +45,14 @@ Rectangle {
"icon": "info"
}]
function navigateNext() {
currentIndex = (currentIndex + 1) % sidebarItems.length
}
function navigatePrevious() {
currentIndex = (currentIndex - 1 + sidebarItems.length) % sidebarItems.length
}
width: 270
height: parent.height
color: Theme.surfaceContainer
@@ -77,7 +87,10 @@ Rectangle {
model: sidebarContainer.sidebarItems
Rectangle {
delegate: Rectangle {
required property int index
required property var modelData
property bool isActive: sidebarContainer.currentIndex === index
width: parent.width - Theme.spacingS * 2

View File

@@ -7,6 +7,10 @@ import qs.Widgets
Rectangle {
id: resultsContainer
// DEVELOPER NOTE: This component renders the Spotlight launcher (accessed via Mod+Space).
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
// likely require corresponding updates in Modules/AppDrawer/AppLauncher.qml and vice versa.
property var appLauncher: null
property var contextMenu: null
@@ -90,19 +94,32 @@ Rectangle {
width: resultsList.iconSize
height: resultsList.iconSize
anchors.verticalCenter: parent.verticalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: resultsList.iconSize
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: listIconImg
anchors.fill: parent
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !listIconImg.visible
visible: !parent.isMaterial && !listIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1
@@ -120,7 +137,7 @@ Rectangle {
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - resultsList.iconSize - Theme.spacingL
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
@@ -255,20 +272,33 @@ Rectangle {
width: iconSize
height: iconSize
anchors.horizontalCenter: parent.horizontalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: parent.iconSize
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: gridIconImg
anchors.fill: parent
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !gridIconImg.visible
visible: !parent.isMaterial && !gridIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1

View File

@@ -15,12 +15,32 @@ DankModal {
property string wifiAnonymousIdentityInput: ""
property string wifiDomainInput: ""
property bool isPromptMode: false
property string promptToken: ""
property string promptReason: ""
property var promptFields: []
property string promptSetting: ""
property bool isVpnPrompt: false
property string connectionName: ""
property string vpnServiceType: ""
property string connectionType: ""
function show(ssid) {
wifiPasswordSSID = ssid
wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
isPromptMode = false
promptToken = ""
promptReason = ""
promptFields = []
promptSetting = ""
isVpnPrompt = false
connectionName = ""
vpnServiceType = ""
connectionType = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
@@ -37,6 +57,46 @@ DankModal {
})
}
function showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
isPromptMode = true
promptToken = token
promptReason = reason
promptFields = fields || []
promptSetting = setting || "802-11-wireless-security"
connectionType = connType || "802-11-wireless"
connectionName = connName || ssid || ""
vpnServiceType = vpnService || ""
isVpnPrompt = (connectionType === "vpn" || connectionType === "wireguard")
wifiPasswordSSID = isVpnPrompt ? connectionName : ssid
requiresEnterprise = setting === "802-1x"
if (reason === "wrong-password") {
wifiPasswordInput = ""
wifiUsernameInput = ""
} else {
wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
}
open()
Qt.callLater(() => {
if (contentLoader.item) {
if (reason === "wrong-password" && contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.text = ""
contentLoader.item.passwordInput.forceActiveFocus()
} else if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
})
}
shouldBeVisible: false
width: 420
height: requiresEnterprise ? 430 : 230
@@ -60,6 +120,9 @@ DankModal {
})
}
onBackgroundClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -90,6 +153,9 @@ DankModal {
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -111,18 +177,42 @@ DankModal {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Connect to Wi-Fi")
text: {
if (isVpnPrompt) {
return I18n.tr("Connect to VPN")
}
return I18n.tr("Connect to Wi-Fi")
}
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: requiresEnterprise ? I18n.tr("Enter credentials for ") + wifiPasswordSSID : I18n.tr("Enter password for ") + wifiPasswordSSID
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
Column {
width: parent.width
elide: Text.ElideRight
spacing: Theme.spacingXS
StyledText {
text: {
if (isVpnPrompt) {
return I18n.tr("Enter password for ") + wifiPasswordSSID
}
const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ")
return prefix + wifiPasswordSSID
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
}
StyledText {
visible: isPromptMode && promptReason === "wrong-password"
text: I18n.tr("Incorrect password")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width
}
}
}
@@ -131,6 +221,9 @@ DankModal {
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -147,7 +240,7 @@ DankModal {
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
MouseArea {
anchors.fill: parent
@@ -200,7 +293,7 @@ DankModal {
textColor: Theme.surfaceText
text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: requiresEnterprise ? I18n.tr("Password") : ""
placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : ""
backgroundColor: "transparent"
focus: !requiresEnterprise
enabled: root.shouldBeVisible
@@ -208,14 +301,28 @@ DankModal {
wifiPasswordInput = text
}
onAccepted: () => {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
if (isPromptMode) {
const secrets = {}
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text
if (passwordInput.text) secrets["password"] = passwordInput.text
if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput
}
NetworkService.submitCredentials(promptToken, secrets, true)
} else {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -257,7 +364,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -289,7 +396,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -395,6 +502,9 @@ DankModal {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -409,7 +519,12 @@ DankModal {
height: 36
radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
enabled: {
if (isVpnPrompt) {
return passwordInput.text.length > 0
}
return requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
}
opacity: enabled ? 1 : 0.5
StyledText {
@@ -430,14 +545,28 @@ DankModal {
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
if (isPromptMode) {
const secrets = {}
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text
if (passwordInput.text) secrets["password"] = passwordInput.text
if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput
}
NetworkService.submitCredentials(promptToken, secrets, true)
} else {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""

View File

@@ -404,16 +404,29 @@ DankPopout {
width: appList.iconSize
height: appList.iconSize
anchors.verticalCenter: parent.verticalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: appList.iconSize - Theme.spacingM
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: listIconImg
anchors.fill: parent
anchors.margins: Theme.spacingXS
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
@@ -421,7 +434,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
visible: !listIconImg.visible
visible: !parent.isMaterial && listIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0
@@ -435,11 +448,12 @@ DankPopout {
font.weight: Font.Bold
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - appList.iconSize - Theme.spacingL
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
@@ -513,6 +527,7 @@ DankPopout {
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset
@@ -578,6 +593,19 @@ DankPopout {
width: iconSize
height: iconSize
anchors.horizontalCenter: parent.horizontalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: parent.iconSize - Theme.spacingL
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: gridIconImg
@@ -586,10 +614,10 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
@@ -597,7 +625,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: !gridIconImg.visible
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0

View File

@@ -8,6 +8,10 @@ import qs.Widgets
Item {
id: root
// DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon).
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
// likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa.
property string searchQuery: ""
property string selectedCategory: I18n.tr("All")
property string viewMode: "list" // "list" or "grid"
@@ -163,7 +167,7 @@ Item {
filteredModel.append({
"name": app.name || "",
"exec": app.execString || app.exec || app.action || "",
"icon": app.icon || "application-x-executable",
"icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"),
"comment": app.comment || "",
"categories": app.categories || [],
"isPlugin": isPluginItem,
@@ -285,12 +289,12 @@ Item {
}
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 = {
@@ -304,7 +308,7 @@ Item {
}
}
}
return { triggered: false, pluginCategory: "", query: "" }
}

View File

@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
Variants {
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens
}
return SettingsData.getFilteredScreens("wallpaper")
}
PanelWindow {
id: blurWallpaperWindow
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "dms:blurwallpaper"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
color: "transparent"
Item {
id: root
anchors.fill: parent
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
property bool isColorSource: source.startsWith("#")
Connections {
target: SessionData
function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || ""
if (newSource !== root.source) {
root.source = newSource
}
}
}
}
function getFillMode(modeName) {
switch(modeName) {
case "Stretch": return Image.Stretch
case "Fit":
case "PreserveAspectFit": return Image.PreserveAspectFit
case "Fill":
case "PreserveAspectCrop": return Image.PreserveAspectCrop
case "Tile": return Image.Tile
case "TileVertically": return Image.TileVertically
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad
default: return Image.PreserveAspectCrop
}
}
WallpaperEngineProc {
id: weProc
monitor: modelData.name
}
Component.onCompleted: {
if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source
wallpaperImage.source = formattedSource
}
}
Component.onDestruction: {
weProc.stop()
}
onSourceChanged: {
const isWE = source.startsWith("we:")
const isColor = source.startsWith("#")
if (isWE) {
wallpaperImage.source = ""
weProc.start(source.substring(3))
} else {
weProc.stop()
if (!source) {
wallpaperImage.source = ""
} else if (isColor) {
wallpaperImage.source = ""
} else {
wallpaperImage.source = source.startsWith("file://") ? source : "file://" + source
}
}
}
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
asynchronous: true
sourceComponent: DankBackdrop {
screenName: modelData.name
}
}
Image {
id: wallpaperImage
anchors.fill: parent
visible: false
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
}
MultiEffect {
anchors.fill: parent
source: wallpaperImage
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -10,26 +10,26 @@ PluginComponent {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!VpnService.connected)
if (!DMSNetworkService.connected)
return "Disconnected"
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: VpnService.connected
ccWidgetIsActive: DMSNetworkService.connected
onCcWidgetToggled: {
if (VpnService.connected) {
VpnService.disconnectAllActive()
} else if (VpnService.profiles.length > 0) {
VpnService.connect(VpnService.profiles[0].uuid)
if (DMSNetworkService.connected) {
DMSNetworkService.disconnectAllActive()
} else if (DMSNetworkService.profiles.length > 0) {
DMSNetworkService.connect(DMSNetworkService.profiles[0].uuid)
}
}
@@ -52,9 +52,9 @@ PluginComponent {
StyledText {
text: {
if (!VpnService.connected)
if (!DMSNetworkService.connected)
return "Active: None"
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
@@ -72,7 +72,7 @@ PluginComponent {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
visible: DMSNetworkService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
@@ -99,7 +99,7 @@ PluginComponent {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
@@ -123,7 +123,7 @@ PluginComponent {
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
@@ -154,7 +154,7 @@ PluginComponent {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -162,9 +162,9 @@ PluginComponent {
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
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
@@ -174,9 +174,9 @@ PluginComponent {
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
@@ -187,7 +187,7 @@ PluginComponent {
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
@@ -234,7 +234,7 @@ PluginComponent {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}

View File

@@ -10,6 +10,7 @@ Item {
property string expandedSection: ""
property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property string screenName: ""
property var pluginDetailInstance: null
property var widgetModel: null
@@ -205,8 +206,9 @@ Item {
Component {
id: brightnessDetailComponent
BrightnessDetail {
currentDeviceName: root.expandedWidgetData?.deviceName || ""
initialDeviceName: root.expandedWidgetData?.deviceName || ""
instanceId: root.expandedWidgetData?.instanceId || ""
screenName: root.screenName
}
}
}

View File

@@ -15,6 +15,8 @@ Column {
property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property bool darkModeTransitionPending: false
property string screenName: ""
property var parentScreen: null
signal expandClicked(var widgetData, int globalIndex)
signal removeWidget(int index)
@@ -182,6 +184,7 @@ Column {
bluetoothCodecSelector: root.bluetoothCodecSelector
widgetModel: root.model
collapseCallback: root.requestCollapse
screenName: root.screenName
}
}
}
@@ -230,18 +233,6 @@ Column {
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":
@@ -341,7 +332,10 @@ Column {
return "Select device"
if (AudioService.sink.audio.muted)
return "Muted"
return Math.round(AudioService.sink.audio.volume * 100) + "%"
const volume = AudioService.sink.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
case "audioInput":
{
@@ -349,7 +343,10 @@ Column {
return "Select device"
if (AudioService.source.audio.muted)
return "Muted"
return Math.round(AudioService.source.audio.volume * 100) + "%"
const volume = AudioService.source.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
default:
return widgetDef?.description || ""
@@ -484,6 +481,8 @@ Column {
height: 14
deviceName: widgetData.deviceName || ""
instanceId: widgetData.instanceId || ""
screenName: root.screenName
parentScreen: root.parentScreen
property color sliderTrackColor: Theme.surfaceContainerHigh
onIconClicked: {

View File

@@ -73,21 +73,21 @@ DankPopout {
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
Qt.callLater(() => {
if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
}
if (UserInfoService)
UserInfoService.getUptime()
})
if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
}
if (UserInfoService)
UserInfoService.getUptime()
})
} else {
Qt.callLater(() => {
if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = false
}
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false
editMode = false
})
if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = false
}
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false
editMode = false
})
}
}
@@ -108,8 +108,7 @@ DankPopout {
return Qt.rgba(surface.r, surface.g, surface.b, transparency)
}
radius: Theme.cornerRadius
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: 0
antialiasing: true
smooth: true
@@ -155,20 +154,22 @@ DankPopout {
model: widgetModel
bluetoothCodecSelector: bluetoothCodecSelector
colorPickerModal: root.colorPickerModal
screenName: root.triggerScreen?.name || ""
parentScreen: root.triggerScreen
onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData
if (widgetData.id === "diskUsage") {
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
} else if (widgetData.id === "brightnessSlider") {
root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default"))
} else {
root.toggleSection(widgetData.id)
}
}
onRemoveWidget: (index) => widgetModel.removeWidget(index)
root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData
if (widgetData.id === "diskUsage") {
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
} else if (widgetData.id === "brightnessSlider") {
root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default"))
} else {
root.toggleSection(widgetData.id)
}
}
onRemoveWidget: index => widgetModel.removeWidget(index)
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index)
onToggleWidgetSize: index => widgetModel.toggleWidgetSize(index)
onCollapseRequested: root.collapseAll()
}
@@ -177,12 +178,13 @@ DankPopout {
visible: editMode
popoutContent: controlContent
availableWidgets: {
if (!editMode) return []
if (!editMode)
return []
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => 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()
onClearAll: () => widgetModel.clearAll()
}
@@ -205,10 +207,10 @@ DankPopout {
id: bluetoothDetailComponent
BluetoothDetail {
id: bluetoothDetail
onShowCodecSelector: function(device) {
onShowCodecSelector: function (device) {
if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) {
contentLoader.item.bluetoothCodecSelector.show(device)
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) {
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
})
}
@@ -233,4 +235,4 @@ DankPopout {
property var colorPickerModal: null
property var powerMenuModalLoader: null
}
}

View File

@@ -17,7 +17,7 @@ Rectangle {
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
Row {
id: headerRow
anchors.left: parent.left
@@ -27,7 +27,7 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS
height: 40
StyledText {
id: headerText
text: I18n.tr("Input Devices")
@@ -37,7 +37,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
id: volumeSlider
anchors.left: parent.left
@@ -105,7 +105,7 @@ Rectangle {
}
}
}
DankFlickable {
id: audioContent
anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
@@ -116,34 +116,34 @@ Rectangle {
anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
contentHeight: audioColumn.height
clip: true
Column {
id: audioColumn
width: parent.width
spacing: Theme.spacingS
Repeater {
model: Pipewire.nodes.values.filter(node => {
return node.audio && !node.isSink && !node.isStream
})
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 50
radius: Theme.cornerRadius
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.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: {
if (modelData.name.includes("bluez"))
@@ -157,11 +157,11 @@ Rectangle {
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
StyledText {
text: AudioService.displayName(modelData)
font.pixelSize: Theme.fontSizeMedium
@@ -171,7 +171,7 @@ Rectangle {
width: parent.width
wrapMode: Text.NoWrap
}
StyledText {
text: modelData === AudioService.source ? "Active" : "Available"
font.pixelSize: Theme.fontSizeSmall
@@ -182,7 +182,7 @@ Rectangle {
}
}
}
MouseArea {
id: deviceMouseArea
anchors.fill: parent

View File

@@ -17,7 +17,7 @@ Rectangle {
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
Row {
id: headerRow
anchors.left: parent.left
@@ -27,7 +27,7 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS
height: 40
StyledText {
id: headerText
text: I18n.tr("Audio Devices")
@@ -121,34 +121,34 @@ Rectangle {
anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM
contentHeight: audioColumn.height
clip: true
Column {
id: audioColumn
width: parent.width
spacing: Theme.spacingS
Repeater {
model: Pipewire.nodes.values.filter(node => {
return node.audio && node.isSink && !node.isStream
})
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 50
radius: Theme.cornerRadius
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.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: {
if (modelData.name.includes("bluez"))
@@ -164,11 +164,11 @@ Rectangle {
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
StyledText {
text: AudioService.displayName(modelData)
font.pixelSize: Theme.fontSizeMedium
@@ -178,7 +178,7 @@ Rectangle {
width: parent.width
wrapMode: Text.NoWrap
}
StyledText {
text: modelData === AudioService.sink ? "Active" : "Available"
font.pixelSize: Theme.fontSizeSmall
@@ -189,7 +189,7 @@ Rectangle {
}
}
}
MouseArea {
id: deviceMouseArea
anchors.fill: parent

View File

@@ -83,12 +83,12 @@ Item {
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: root.hide()
onWheel: (wheel) => { wheel.accepted = true }
onPositionChanged: (mouse) => { mouse.accepted = true }
}
Rectangle {
id: modalBackground
anchors.fill: parent

View File

@@ -5,18 +5,45 @@ import Quickshell.Bluetooth
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modals
Rectangle {
id: root
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
property var bluetoothCodecModalRef: null
property var devicesBeingPaired: new Set()
signal showCodecSelector(var device)
function isDeviceBeingPaired(deviceAddress) {
return devicesBeingPaired.has(deviceAddress)
}
function handlePairDevice(device) {
if (!device) return
const deviceAddr = device.address
devicesBeingPaired.add(deviceAddr)
devicesBeingPairedChanged()
BluetoothService.pairDevice(device, function(response) {
devicesBeingPaired.delete(deviceAddr)
devicesBeingPairedChanged()
if (response.error) {
ToastService.showError(I18n.tr("Pairing failed"), response.error)
} else if (!BluetoothService.enhancedPairingAvailable) {
ToastService.showSuccess(I18n.tr("Device paired"))
}
})
}
function updateDeviceCodecDisplay(deviceAddress, codecName) {
for (let i = 0; i < pairedRepeater.count; i++) {
let item = pairedRepeater.itemAt(i)
@@ -26,7 +53,7 @@ Rectangle {
}
}
}
Row {
id: headerRow
anchors.left: parent.left
@@ -36,7 +63,7 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS
height: 40
StyledText {
id: headerText
text: I18n.tr("Bluetooth Settings")
@@ -45,12 +72,12 @@ Rectangle {
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: Math.max(0, parent.width - headerText.implicitWidth - scanButton.width - Theme.spacingM)
height: parent.height
}
Rectangle {
id: scanButton
width: 100
@@ -64,18 +91,18 @@ Rectangle {
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
size: 18
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan"
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
@@ -84,7 +111,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: scanMouseArea
anchors.fill: parent
@@ -98,7 +125,7 @@ Rectangle {
}
}
}
DankFlickable {
id: bluetoothContent
anchors.top: headerRow.bottom
@@ -110,19 +137,19 @@ Rectangle {
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
contentHeight: bluetoothColumn.height
clip: true
Column {
id: bluetoothColumn
width: parent.width
spacing: Theme.spacingS
Repeater {
id: pairedRepeater
model: {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return []
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
devices.sort((a, b) => {
if (a.connected && !b.connected) return -1
@@ -131,17 +158,17 @@ Rectangle {
})
return devices
}
delegate: Rectangle {
required property var modelData
required property int index
property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || ""
width: parent.width
height: 50
radius: Theme.cornerRadius
Component.onCompleted: {
if (modelData.connected && BluetoothService.isAudioDevice(modelData)) {
BluetoothService.refreshDeviceCodec(modelData)
@@ -162,13 +189,13 @@ Rectangle {
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
border.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: BluetoothService.getDeviceIcon(modelData)
size: Theme.iconSize - 4
@@ -181,11 +208,11 @@ Rectangle {
}
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: 200
StyledText {
text: modelData.name || modelData.deviceName || "Unknown Device"
font.pixelSize: Theme.fontSizeMedium
@@ -194,10 +221,10 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
}
Row {
spacing: Theme.spacingXS
StyledText {
text: {
if (modelData.state === BluetoothDeviceState.Connecting)
@@ -218,12 +245,12 @@ Rectangle {
return Theme.surfaceVariantText
}
}
StyledText {
text: {
if (modelData.batteryAvailable && modelData.battery > 0)
return "• " + Math.round(modelData.battery * 100) + "%"
var btBattery = BatteryService.bluetoothDevices.find(dev => {
return dev.name === (modelData.name || modelData.deviceName) ||
dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) ||
@@ -235,7 +262,7 @@ Rectangle {
color: Theme.surfaceVariantText
visible: text.length > 0
}
StyledText {
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
font.pixelSize: Theme.fontSizeSmall
@@ -245,7 +272,7 @@ Rectangle {
}
}
}
DankActionButton {
id: pairedOptionsButton
anchors.right: parent.right
@@ -262,7 +289,7 @@ Rectangle {
}
}
}
MouseArea {
id: deviceMouseArea
anchors.fill: parent
@@ -279,26 +306,26 @@ Rectangle {
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
visible: pairedRepeater.count > 0 && availableRepeater.count > 0
}
Item {
width: parent.width
height: 80
visible: BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
DankIcon {
anchors.centerIn: parent
name: "sync"
size: 24
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.4)
RotationAnimation on rotation {
running: parent.visible && BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
loops: Animation.Infinite
@@ -308,52 +335,52 @@ Rectangle {
}
}
}
Repeater {
id: availableRepeater
model: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
return []
var filtered = Bluetooth.devices.values.filter(dev => {
return dev && !dev.paired && !dev.pairing && !dev.blocked &&
(dev.signalStrength === undefined || dev.signalStrength > 0)
})
return BluetoothService.sortDevices(filtered)
}
delegate: Rectangle {
required property var modelData
required property int index
property bool canConnect: BluetoothService.canConnect(modelData)
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
property bool isBusy: BluetoothService.isDeviceBusy(modelData) || isDeviceBeingPaired(modelData.address)
width: parent.width
height: 50
radius: Theme.cornerRadius
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.width: 0
opacity: canConnect ? 1 : 0.6
opacity: (canConnect && !isBusy) ? 1 : 0.6
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: BluetoothService.getDeviceIcon(modelData)
size: Theme.iconSize - 4
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: 200
StyledText {
text: modelData.name || modelData.deviceName || "Unknown Device"
font.pixelSize: Theme.fontSizeMedium
@@ -361,20 +388,20 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
}
Row {
spacing: Theme.spacingXS
StyledText {
text: {
if (modelData.pairing) return "Pairing..."
if (modelData.pairing || isBusy) return "Pairing..."
if (modelData.blocked) return "Blocked"
return BluetoothService.getSignalStrength(modelData)
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
font.pixelSize: Theme.fontSizeSmall
@@ -384,21 +411,21 @@ Rectangle {
}
}
}
StyledText {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: {
if (modelData.pairing) return "Pairing..."
if (isBusy) return "Pairing..."
if (!canConnect) return "Cannot pair"
return "Pair"
}
font.pixelSize: Theme.fontSizeSmall
color: canConnect ? Theme.primary : Theme.surfaceVariantText
color: (canConnect && !isBusy) ? Theme.primary : Theme.surfaceVariantText
font.weight: Font.Medium
}
MouseArea {
id: availableMouseArea
anchors.fill: parent
@@ -406,20 +433,18 @@ Rectangle {
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: canConnect && !isBusy
onClicked: {
if (modelData) {
BluetoothService.connectDeviceWithTrust(modelData)
}
root.handlePairDevice(modelData)
}
}
}
}
Item {
width: parent.width
height: 60
visible: !BluetoothService.adapter
StyledText {
anchors.centerIn: parent
text: I18n.tr("No Bluetooth adapter found")
@@ -429,25 +454,25 @@ Rectangle {
}
}
}
Menu {
id: bluetoothContextMenu
width: 150
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
property var currentDevice: null
background: Rectangle {
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
MenuItem {
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect"
height: 32
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
@@ -455,12 +480,12 @@ Rectangle {
leftPadding: Theme.spacingS
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: {
if (bluetoothContextMenu.currentDevice) {
if (bluetoothContextMenu.currentDevice.connected) {
@@ -471,12 +496,12 @@ Rectangle {
}
}
}
MenuItem {
text: I18n.tr("Audio Codec")
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
@@ -484,23 +509,23 @@ Rectangle {
leftPadding: Theme.spacingS
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: {
if (bluetoothContextMenu.currentDevice) {
showCodecSelector(bluetoothContextMenu.currentDevice)
}
}
}
MenuItem {
text: I18n.tr("Forget Device")
height: 32
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
@@ -508,18 +533,38 @@ Rectangle {
leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
if (bluetoothContextMenu.currentDevice) {
bluetoothContextMenu.currentDevice.forget()
if (BluetoothService.enhancedPairingAvailable) {
const devicePath = BluetoothService.getDevicePath(bluetoothContextMenu.currentDevice)
DMSService.bluetoothRemove(devicePath, response => {
if (response.error) {
ToastService.showError(I18n.tr("Failed to remove device"), response.error)
}
})
} else {
bluetoothContextMenu.currentDevice.forget()
}
}
}
}
}
BluetoothPairingModal {
id: bluetoothPairingModal
}
Connections {
target: DMSService
function onBluetoothPairingRequest(data) {
bluetoothPairingModal.show(data)
}
}
}

View File

@@ -8,11 +8,76 @@ import qs.Widgets
Rectangle {
id: root
property string currentDeviceName: ""
property string initialDeviceName: ""
property string instanceId: ""
property string screenName: ""
signal deviceNameChanged(string newDeviceName)
property string currentDeviceName: ""
function resolveDeviceName() {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
return ""
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
if (found) {
return found.name
}
}
}
if (initialDeviceName && initialDeviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName)
if (found) {
return found.name
}
}
const currentDeviceNameFromService = DisplayService.currentDevice
if (currentDeviceNameFromService) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService)
if (found) {
return found.name
}
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
}
Component.onCompleted: {
currentDeviceName = resolveDeviceName()
}
property bool isPinnedToScreen: {
if (!screenName || screenName.length === 0) {
return false
}
const pins = SettingsData.brightnessDevicePins || {}
return pins[screenName] === currentDeviceName
}
function togglePinToScreen() {
if (!screenName || screenName.length === 0 || !currentDeviceName || currentDeviceName.length === 0) {
return
}
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
if (isPinnedToScreen) {
delete pins[screenName]
} else {
pins[screenName] = currentDeviceName
}
SettingsData.setBrightnessDevicePins(pins)
}
implicitHeight: brightnessContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
@@ -61,6 +126,74 @@ Rectangle {
}
}
Rectangle {
width: parent.width
height: 40
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "monitor"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: screenName || "Unknown Monitor"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: pinRow.width + Theme.spacingS * 2
height: 28
radius: height / 2
color: isPinnedToScreen ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
Row {
id: pinRow
anchors.centerIn: parent
spacing: 4
DankIcon {
name: isPinnedToScreen ? "push_pin" : "push_pin"
size: 16
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: isPinnedToScreen ? "Pinned" : "Pin"
font.pixelSize: Theme.fontSizeSmall
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.togglePinToScreen()
}
}
}
}
Repeater {
model: DisplayService.devices || []
delegate: Rectangle {
@@ -90,7 +223,7 @@ Rectangle {
const deviceName = modelData.name || ""
if (deviceClass === "backlight" || deviceClass === "ddc") {
const brightness = modelData.percentage || 50
const brightness = DisplayService.getDeviceBrightness(modelData.name)
if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium"
return "brightness_high"
@@ -106,7 +239,7 @@ Rectangle {
}
StyledText {
text: (modelData.percentage || 50) + "%"
text: Math.round(DisplayService.getDeviceBrightness(modelData.name)) + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -28,7 +28,31 @@ Rectangle {
Component.onDestruction: {
NetworkService.removeRef()
}
property int currentPreferenceIndex: {
if (DMSService.apiVersion < 5) {
return 1
}
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
return 1
}
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
}
Row {
id: headerRow
anchors.left: parent.left
@@ -38,7 +62,7 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingS
height: 40
StyledText {
id: headerText
text: I18n.tr("Network Settings")
@@ -47,32 +71,16 @@ Rectangle {
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM)
height: parent.height
}
DankButtonGroup {
id: preferenceControls
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.ethernetConnected
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
}
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex
@@ -84,7 +92,7 @@ Rectangle {
}
}
}
Item {
id: wifiToggleContent
anchors.top: headerRow.bottom
@@ -92,7 +100,7 @@ Rectangle {
anchors.right: parent.right
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: NetworkService.wifiToggling
visible: currentPreferenceIndex === 1 && NetworkService.wifiToggling
height: visible ? 80 : 0
Column {
@@ -123,7 +131,7 @@ Rectangle {
}
}
}
Item {
id: wifiOffContent
anchors.top: headerRow.bottom
@@ -131,21 +139,21 @@ Rectangle {
anchors.right: parent.right
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: !NetworkService.wifiEnabled && !NetworkService.wifiToggling
visible: currentPreferenceIndex === 1 && !NetworkService.wifiEnabled && !NetworkService.wifiToggling
height: visible ? 120 : 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingL
width: parent.width
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: "wifi_off"
size: 48
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: I18n.tr("WiFi is off")
@@ -154,7 +162,7 @@ Rectangle {
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 120
@@ -163,7 +171,7 @@ Rectangle {
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: 0
border.color: Theme.primary
StyledText {
anchors.centerIn: parent
text: I18n.tr("Enable WiFi")
@@ -171,7 +179,7 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
}
MouseArea {
id: enableWifiButton
anchors.fill: parent
@@ -179,7 +187,179 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: NetworkService.toggleWifiRadio()
}
}
}
}
DankFlickable {
id: wiredContent
anchors.top: headerRow.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 0 && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
contentHeight: wiredColumn.height
clip: true
Column {
id: wiredColumn
width: parent.width
spacing: Theme.spacingS
Repeater {
model: sortedNetworks
property var sortedNetworks: {
const currentUuid = NetworkService.ethernetConnectionUuid
const networks = NetworkService.wiredConnections
let sorted = [...networks]
sorted.sort((a, b) => {
if (a.isActive && !b.isActive) return -1
if (!a.isActive && b.isActive) return 1
return a.id.localeCompare(b.id)
})
return sorted
}
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 50
radius: Theme.cornerRadius
color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: Theme.primary
border.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: "lan"
size: Theme.iconSize - 4
color: modelData.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: 200
StyledText {
text: modelData.id || "Unknown Config"
font.pixelSize: Theme.fontSizeMedium
color: modelData.isActive ? Theme.primary : Theme.surfaceText
font.weight: modelData.isActive ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
}
}
DankActionButton {
id: wiredOptionsButton
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
iconName: "more_horiz"
buttonSize: 28
onClicked: {
if (wiredNetworkContextMenu.visible) {
wiredNetworkContextMenu.close()
} else {
wiredNetworkContextMenu.currentID = modelData.id
wiredNetworkContextMenu.currentUUID = modelData.uuid
wiredNetworkContextMenu.currentConnected = modelData.isActive
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS)
}
}
}
MouseArea {
id: wiredNetworkMouseArea
anchors.fill: parent
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: function(event) {
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
NetworkService.connectToSpecificWiredConfig(modelData.uuid)
}
event.accepted = true
}
}
}
}
}
}
Menu {
id: wiredNetworkContextMenu
width: 150
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
property string currentID: ""
property string currentUUID: ""
property bool currentConnected: false
background: Rectangle {
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
MenuItem {
text: "Activate"
height: !wiredNetworkContextMenu.currentConnected ? 32 : 0
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
leftPadding: Theme.spacingS
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: {
if (!networkContextMenu.currentConnected) {
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID)
}
}
}
MenuItem {
text: I18n.tr("Network Info")
height: wiredNetworkContextMenu.currentConnected ? 32 : 0
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
leftPadding: Theme.spacingS
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: {
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID)
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData)
}
}
}
@@ -192,15 +372,15 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: NetworkService.wifiInterface && NetworkService.wifiEnabled && !NetworkService.wifiToggling
visible: currentPreferenceIndex === 1 && NetworkService.wifiEnabled && !NetworkService.wifiToggling
contentHeight: wifiColumn.height
clip: true
Column {
id: wifiColumn
width: parent.width
spacing: Theme.spacingS
Item {
width: parent.width
height: 200
@@ -221,7 +401,7 @@ Rectangle {
}
}
}
Repeater {
model: sortedNetworks
@@ -239,20 +419,20 @@ Rectangle {
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 50
radius: Theme.cornerRadius
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.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: {
let strength = modelData.signal || 0
@@ -264,11 +444,11 @@ Rectangle {
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: 200
StyledText {
text: modelData.ssid || "Unknown Network"
font.pixelSize: Theme.fontSizeMedium
@@ -282,27 +462,27 @@ Rectangle {
spacing: Theme.spacingXS
StyledText {
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open")
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: modelData.saved ? "Saved" : ""
text: modelData.saved ? "Saved" : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
visible: text.length > 0
}
StyledText {
text: "• " + modelData.signal + "%"
text: (modelData.saved ? "• " : "") + modelData.signal + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
}
DankActionButton {
id: optionsButton
anchors.right: parent.right
@@ -323,7 +503,7 @@ Rectangle {
}
}
}
MouseArea {
id: networkMouseArea
anchors.fill: parent
@@ -333,7 +513,11 @@ Rectangle {
onClicked: function(event) {
if (modelData.ssid !== NetworkService.currentWifiSSID) {
if (modelData.secured && !modelData.saved) {
wifiPasswordModal.show(modelData.ssid)
if (DMSService.apiVersion >= 7) {
NetworkService.connectToWifi(modelData.ssid)
} else if (PopoutService.wifiPasswordModal) {
PopoutService.wifiPasswordModal.show(modelData.ssid)
}
} else {
NetworkService.connectToWifi(modelData.ssid)
}
@@ -341,34 +525,34 @@ Rectangle {
event.accepted = true
}
}
}
}
}
}
Menu {
id: networkContextMenu
width: 150
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
property string currentSSID: ""
property bool currentSecured: false
property bool currentConnected: false
property bool currentSaved: false
property int currentSignal: 0
background: Rectangle {
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
MenuItem {
text: networkContextMenu.currentConnected ? "Disconnect" : "Connect"
height: 32
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
@@ -376,29 +560,33 @@ Rectangle {
leftPadding: Theme.spacingS
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: {
if (networkContextMenu.currentConnected) {
NetworkService.disconnectWifi()
} else {
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
wifiPasswordModal.show(networkContextMenu.currentSSID)
if (DMSService.apiVersion >= 7) {
NetworkService.connectToWifi(networkContextMenu.currentSSID)
} else if (PopoutService.wifiPasswordModal) {
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
}
} else {
NetworkService.connectToWifi(networkContextMenu.currentSSID)
}
}
}
}
MenuItem {
text: I18n.tr("Network Info")
height: 32
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
@@ -406,23 +594,23 @@ Rectangle {
leftPadding: Theme.spacingS
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: {
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
}
}
MenuItem {
text: I18n.tr("Forget Network")
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
@@ -430,25 +618,23 @@ Rectangle {
leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID)
}
}
}
WifiPasswordModal {
id: wifiPasswordModal
}
NetworkInfoModal {
id: networkInfoModal
}
NetworkWiredInfoModal {
id: networkWiredInfoModal
}
}

View File

@@ -143,8 +143,8 @@ QtObject {
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": VpnService.available,
"warning": !VpnService.available ? "VPN not available" : undefined,
"enabled": DMSNetworkService.available,
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]

View File

@@ -1,63 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSource: AudioService.source
iconName: {
if (!defaultSource) return "mic_off"
let volume = defaultSource.audio.volume
let muted = defaultSource.audio.muted
if (muted || volume === 0.0) return "mic_off"
return "mic"
}
isActive: defaultSource && !defaultSource.audio.muted
primaryText: {
if (!defaultSource) {
return "No input device"
}
return defaultSource.description || "Audio Input"
}
secondaryText: {
if (!defaultSource) {
return "Select device"
}
if (defaultSource.audio.muted) {
return "Muted"
}
return Math.round(defaultSource.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSource && defaultSource.audio) {
defaultSource.audio.muted = !defaultSource.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSource || !defaultSource.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSource.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSource.audio.muted = false
defaultSource.audio.volume = newVolume / 100
wheelEvent.accepted = true
}
}

View File

@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSink: AudioService.sink
iconName: {
if (!defaultSink) return "volume_off"
let volume = defaultSink.audio.volume
let muted = defaultSink.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"
}
isActive: defaultSink && !defaultSink.audio.muted
primaryText: {
if (!defaultSink) {
return "No output device"
}
return defaultSink.description || "Audio Output"
}
secondaryText: {
if (!defaultSink) {
return "Select device"
}
if (defaultSink.audio.muted) {
return "Muted"
}
return Math.round(defaultSink.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSink && defaultSink.audio) {
defaultSink.audio.muted = !defaultSink.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSink || !defaultSink.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSink.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSink.audio.muted = false
defaultSink.audio.volume = newVolume / 100
AudioService.volumeChanged()
wheelEvent.accepted = true
}
}

View File

@@ -1,70 +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
property var 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
}
iconName: {
if (!BluetoothService.available) {
return "bluetooth_disabled"
}
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "bluetooth_disabled"
}
return "bluetooth"
}
isActive: !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
showExpandArea: BluetoothService.available
primaryText: {
if (!BluetoothService.available) {
return "Bluetooth"
}
if (!BluetoothService.adapter) {
return "No adapter"
}
if (!BluetoothService.adapter.enabled) {
return "Disabled"
}
return "Enabled"
}
secondaryText: {
if (!BluetoothService.available) {
return "No adapters"
}
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "Off"
}
if (primaryDevice) {
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
}
return "No devices"
}
onToggled: {
if (BluetoothService.available && BluetoothService.adapter) {
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
}
}
}

View File

@@ -10,6 +10,8 @@ Row {
property string deviceName: ""
property string instanceId: ""
property string screenName: ""
property var parentScreen: null
signal iconClicked()
@@ -21,6 +23,17 @@ Row {
return ""
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
if (found) {
return found.name
}
}
}
if (deviceName && deviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === deviceName)
return found ? found.name : ""
@@ -76,8 +89,10 @@ Row {
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)
const globalPos = iconArea.mapToGlobal(iconArea.width / 2, iconArea.height / 2)
const screenY = root.parentScreen?.y ?? 0
const relativeY = globalPos.y - screenY - 55
tooltipLoader.item.show(tooltipText, globalPos.x, relativeY, root.parentScreen)
}
}
@@ -121,7 +136,7 @@ Row {
value: targetBrightness
onSliderValueChanged: function(newValue) {
if (DisplayService.brightnessAvailable && targetDeviceName) {
DisplayService.setBrightness(newValue, targetDeviceName)
DisplayService.setBrightness(newValue, targetDeviceName, true)
}
}
thumbOutlineColor: Theme.surfaceContainer

View File

@@ -14,7 +14,7 @@ Rectangle {
property real maximumValue: 1.0
property real minimumValue: 0.0
property bool enabled: true
signal sliderValueChanged(real value)
width: parent ? parent.width : 200

View File

@@ -244,7 +244,6 @@ Item {
Canvas {
id: barBorder
anchors.fill: parent
antialiasing: false
visible: SettingsData.dankBarBorderEnabled
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
@@ -257,6 +256,8 @@ Item {
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
property bool borderEnabled: SettingsData.dankBarBorderEnabled
antialiasing: rt > 0 || wing > 0
onWingChanged: root.requestRepaint()
onRtChanged: root.requestRepaint()
onBorderEnabledChanged: root.requestRepaint()
@@ -306,38 +307,6 @@ Item {
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)
@@ -353,20 +322,85 @@ Item {
ctx.rotate(Math.PI / 2)
}
drawTopBorder()
ctx.restore()
const uiThickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
const devThickness = Math.max(1, Math.round(Theme.px(uiThickness, dpr)))
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()
ctx.fillStyle = color
function drawTopBorder() {
if (!hasEdgeGap) {
ctx.beginPath()
ctx.rect(0, H - devThickness, W, devThickness)
ctx.fill()
} else {
const thk = devThickness
const RTi = Math.max(0, RT - thk)
const Ri = Math.max(0, R - thk)
ctx.beginPath()
if (R > 0 && Ri > 0) {
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H)
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)
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
ctx.closePath()
ctx.moveTo(RT, thk)
ctx.arcTo(thk, thk, thk, RT, RTi)
ctx.lineTo(thk, H + R)
ctx.arc(R, H + R, Ri, -Math.PI, -Math.PI / 2, false)
ctx.lineTo(W - R, H + thk)
ctx.arc(W - R, H + R, Ri, -Math.PI / 2, 0, false)
ctx.lineTo(W - thk, H + R)
ctx.lineTo(W - thk, RT)
ctx.arcTo(W - thk, thk, W - RT, thk, RTi)
ctx.lineTo(RT, thk)
ctx.closePath()
} else {
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
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.moveTo(RT, thk)
ctx.arcTo(thk, thk, thk, RT, RTi)
ctx.lineTo(thk, H - RT)
ctx.arcTo(thk, H - thk, RT, H - thk, RTi)
ctx.lineTo(W - RT, H - thk)
ctx.arcTo(W - thk, H - thk, W - thk, H - RT, RTi)
ctx.lineTo(W - thk, RT)
ctx.arcTo(W - thk, thk, W - RT, thk, RTi)
ctx.lineTo(RT, thk)
ctx.closePath()
}
ctx.fill("evenodd")
}
}
drawTopBorder()
ctx.restore()
}
}
}

View File

@@ -13,8 +13,10 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
readonly property real spacing: noBackground ? 2 : Theme.spacingXS
property var centerWidgets: []
@@ -396,8 +398,47 @@ Item {
if ("barThickness" in item) {
item.barThickness = Qt.binding(() => root.barThickness)
}
if ("sectionSpacing" in item) {
item.sectionSpacing = Qt.binding(() => root.spacing)
}
if ("isFirst" in item) {
item.isFirst = Qt.binding(() => {
for (var i = 0; i < centerRepeater.count; i++) {
const checkItem = centerRepeater.itemAt(i)
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item
}
}
return false
})
}
if ("isLast" in item) {
item.isLast = Qt.binding(() => {
for (var i = centerRepeater.count - 1; i >= 0; i--) {
const checkItem = centerRepeater.itemAt(i)
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item
}
}
return false
})
}
if ("isLeftBarEdge" in item) {
item.isLeftBarEdge = false
}
if ("isRightBarEdge" in item) {
item.isRightBarEdge = false
}
if ("isTopBarEdge" in item) {
item.isTopBarEdge = false
}
if ("isBottomBarEdge" in item) {
item.isBottomBarEdge = false
}
// Inject PluginService for plugin widgets
if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":")
var pluginId = parts[0]

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,10 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
implicitHeight: layoutLoader.item ? (layoutLoader.item.implicitHeight || layoutLoader.item.height) : 0
implicitWidth: layoutLoader.item ? (layoutLoader.item.implicitWidth || layoutLoader.item.width) : 0
@@ -26,10 +28,13 @@ Item {
Component {
id: rowComp
Row {
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater {
id: rowRepeater
model: root.widgetsModel
Item {
readonly property real rowSpacing: parent.widgetSpacing
width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -45,6 +50,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: true
isRightBarEdge: false
}
}
}
@@ -55,10 +65,13 @@ Item {
id: columnComp
Column {
width: Math.max(parent.width, 200)
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater {
id: columnRepeater
model: root.widgetsModel
Item {
readonly property real columnSpacing: parent.widgetSpacing
width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -74,6 +87,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: true
isBottomBarEdge: false
}
}
}

View File

@@ -235,7 +235,7 @@ DankPopout {
}
StyledText {
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
text: BatteryService.batteryStatus
font.pixelSize: Theme.fontSizeLarge
color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
@@ -247,6 +247,7 @@ DankPopout {
return Theme.surfaceText;
}
font.weight: Font.Medium
visible: BatteryService.batteryAvailable
anchors.verticalCenter: parent.verticalCenter
}
}

View File

@@ -14,7 +14,7 @@ DankPopout {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
property var triggerScreen: null
@@ -161,11 +161,11 @@ DankPopout {
StyledText {
text: {
if (!VpnService.connected) {
if (!DMSNetworkService.connected) {
return "Active: None";
}
const names = VpnService.activeNames || [];
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1) {
return "Active: " + (names[0] || "VPN");
}
@@ -193,7 +193,7 @@ DankPopout {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
visible: DMSNetworkService.connected
width: 130
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
border.width: 0
@@ -224,7 +224,7 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
onClicked: DMSNetworkService.disconnectAllActive()
}
}
@@ -251,7 +251,7 @@ DankPopout {
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
@@ -284,7 +284,7 @@ DankPopout {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -292,9 +292,9 @@ DankPopout {
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
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
@@ -304,9 +304,9 @@ DankPopout {
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
@@ -317,7 +317,7 @@ DankPopout {
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
@@ -392,7 +392,7 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}

View File

@@ -11,8 +11,10 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
@@ -27,11 +29,14 @@ Item {
Component {
id: rowComp
Row {
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
anchors.right: parent ? parent.right : undefined
Repeater {
id: rowRepeater
model: root.widgetsModel
Item {
readonly property real rowSpacing: parent.widgetSpacing
width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -47,6 +52,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: false
isRightBarEdge: true
}
}
}
@@ -57,10 +67,13 @@ Item {
id: columnComp
Column {
width: parent ? parent.width : 0
spacing: noBackground ? 2 : Theme.spacingXS
readonly property real widgetSpacing: noBackground ? 2 : Theme.spacingXS
spacing: widgetSpacing
Repeater {
id: columnRepeater
model: root.widgetsModel
Item {
readonly property real columnSpacing: parent.widgetSpacing
width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
@@ -76,6 +89,11 @@ Item {
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
isFirst: model.index === 0
isLast: model.index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: false
isBottomBarEdge: true
}
}
}

View File

@@ -15,10 +15,20 @@ Loader {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool isFirst: false
property bool isLast: false
property real sectionSpacing: 0
property bool isLeftBarEdge: false
property bool isRightBarEdge: false
property bool isTopBarEdge: false
property bool isBottomBarEdge: false
asynchronous: false
active: getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
readonly property bool orientationMatches: (axis?.isVertical ?? false) === isInColumn
active: orientationMatches &&
getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
(widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: getWidgetComponent(widgetId, components)
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
@@ -73,6 +83,62 @@ Loader {
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isFirst" in root.item
property: "isFirst"
value: root.isFirst
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isLast" in root.item
property: "isLast"
value: root.isLast
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "sectionSpacing" in root.item
property: "sectionSpacing"
value: root.sectionSpacing
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isLeftBarEdge" in root.item
property: "isLeftBarEdge"
value: root.isLeftBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isRightBarEdge" in root.item
property: "isRightBarEdge"
value: root.isRightBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isTopBarEdge" in root.item
property: "isTopBarEdge"
value: root.isTopBarEdge
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "isBottomBarEdge" in root.item
property: "isBottomBarEdge"
value: root.isBottomBarEdge
restoreMode: Binding.RestoreNone
}
onLoaded: {
if (item) {
contentItemReady(item)

View File

@@ -1,126 +1,114 @@
import QtQuick
import Quickshell.Services.UPower
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
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))
property var popoutTarget: null
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
content: Component {
Item {
implicitWidth: battery.isVerticalOrientation ? (battery.widgetThickness - battery.horizontalPadding * 2) : batteryContent.implicitWidth
implicitHeight: battery.isVerticalOrientation ? batteryColumn.implicitHeight : (battery.widgetThickness - battery.horizontalPadding * 2)
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(barThickness)
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText
Column {
id: batteryColumn
visible: battery.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(battery.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
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error
StyledText {
text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.barTextSize(battery.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
}
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.isVerticalOrientation
anchors.centerIn: parent
spacing: SettingsData.dankBarNoBackground ? 1 : 2
Row {
id: batteryContent
visible: !battery.isVertical
anchors.centerIn: parent
spacing: SettingsData.dankBarNoBackground ? 1 : 2
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(battery.barThickness, -4)
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText;
}
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
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error;
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.barTextSize(battery.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
}
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
x: -battery.leftMargin
y: -battery.topMargin
width: battery.width + battery.leftMargin + battery.rightMargin
height: battery.height + battery.topMargin + battery.bottomMargin
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = battery.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, battery.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
toggleBatteryPopup();
toggleBatteryPopup()
}
}
}

View File

@@ -1,57 +1,25 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Widgets
Item {
BasePill {
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()
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
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"
DankIcon {
anchors.centerIn: parent
name: "content_paste"
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
}
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
}
}
}

View File

@@ -1,270 +1,257 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Modules.Plugins
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool compactMode: false
property string section: "center"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
signal clockClicked
width: isVertical ? widgetThickness : (clockRow.implicitWidth + horizontalPadding * 2)
height: isVertical ? (clockColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = clockMouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Column {
id: clockColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: -2
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(0)
}
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(1)
}
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
Row {
visible: SettingsData.showSeconds
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
content: Component {
Item {
width: 12
height: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : clockRow.implicitWidth
implicitHeight: root.isVerticalOrientation ? clockColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Rectangle {
width: 12
height: 1
color: Theme.outlineButton
Column {
id: clockColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
}
}
spacing: -2
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(0)
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(0)
}
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(0)
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
}
}
}
Row {
id: clockRow
visible: !root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingS
StyledText {
text: {
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.clockCompactMode
}
StyledText {
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1)
} else {
const hours = systemClock?.date?.getHours()
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
return String(display).padStart(2, '0').charAt(1)
}
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.clockCompactMode
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
SystemClock {
id: systemClock
precision: SystemClock.Seconds
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Row {
visible: SettingsData.showSeconds
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Item {
width: 12
height: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 12
height: 1
color: Theme.outlineButton
anchors.centerIn: parent
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: {
const locale = Qt.locale()
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
return value.charAt(1)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
}
Row {
id: clockRow
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: Theme.spacingS
StyledText {
id: timeText
text: {
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.baseline: dateText.baseline
}
StyledText {
id: middleDot
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.baseline: dateText.baseline
visible: !SettingsData.clockCompactMode
}
StyledText {
id: dateText
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
}
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !SettingsData.clockCompactMode
}
}
SystemClock {
id: systemClock
precision: SystemClock.Seconds
}
}
}
MouseArea {
id: clockMouseArea
anchors.fill: parent
hoverEnabled: true
x: -root.leftMargin
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
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)
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.visualWidth)
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
}
root.clockClicked()
}
}
}

View File

@@ -1,54 +1,35 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool isActive: 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 clicked()
width: isVertical ? widgetThickness : (colorPickerIcon.width + horizontalPadding * 2)
height: isVertical ? (colorPickerIcon.height + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = colorPickerArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
id: colorPickerIcon
anchors.centerIn: parent
name: "palette"
size: Theme.barIconSize(barThickness, -4)
color: colorPickerArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: colorPickerArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.colorPickerRequested();
}
}
signal colorPickerRequested()
}
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
DankIcon {
anchors.centerIn: parent
name: "palette"
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.surfaceText
}
}
}
MouseArea {
z: 1
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: {
root.colorPickerRequested()
}
}
}

View File

@@ -1,266 +1,245 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool isActive: false
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property var popoutTarget: null
property var widgetData: null
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
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()
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : controlIndicators.implicitWidth
implicitHeight: root.isVerticalOrientation ? controlColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
width: isVertical ? widgetThickness : (controlIndicators.implicitWidth + horizontalPadding * 2)
height: isVertical ? (controlColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
Column {
id: controlColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: Theme.spacingXS
const baseColor = controlCenterArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
name: {
if (NetworkService.wifiToggling) {
return "sync"
}
Column {
id: controlColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
if (NetworkService.networkStatus === "ethernet") {
return "lan"
}
DankIcon {
name: {
if (NetworkService.wifiToggling) {
return "sync"
return NetworkService.wifiSignalIcon
}
size: Theme.barIconSize(root.barThickness)
color: {
if (NetworkService.wifiToggling) {
return Theme.primary
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
}
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
}
if (NetworkService.networkStatus === "ethernet") {
return "lan"
DankIcon {
name: "bluetooth"
size: Theme.barIconSize(root.barThickness)
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
return NetworkService.wifiSignalIcon
}
size: Theme.barIconSize(barThickness)
color: {
if (NetworkService.wifiToggling) {
return Theme.primary
}
Rectangle {
width: audioIconV.implicitWidth + 4
height: audioIconV.implicitHeight + 4
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showAudioIcon
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
}
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
}
DankIcon {
id: audioIconV
DankIcon {
name: "bluetooth"
size: Theme.barIconSize(barThickness)
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
Rectangle {
width: audioIconV.implicitWidth + 4
height: audioIconV.implicitHeight + 4
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIconV
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off"
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down"
} else {
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off"
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down"
} else {
return "volume_up"
}
}
return "volume_up"
}
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.centerIn: parent
}
return "volume_up"
}
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
let newVolume
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5)
} else {
newVolume = Math.max(0, currentVolume - 5)
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false
AudioService.sink.audio.volume = newVolume / 100
}
wheelEvent.accepted = true
}
}
}
DankIcon {
name: "settings"
size: Theme.barIconSize(barThickness)
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
}
Row {
id: controlIndicators
visible: !root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
id: networkIcon
name: {
if (NetworkService.wifiToggling) {
return "sync";
}
if (NetworkService.networkStatus === "ethernet") {
return "lan";
}
return NetworkService.wifiSignalIcon;
}
size: Theme.barIconSize(barThickness)
color: {
if (NetworkService.wifiToggling) {
return Theme.primary;
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
}
anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
}
DankIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.barIconSize(barThickness)
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
Rectangle {
width: audioIcon.implicitWidth + 4
height: audioIcon.implicitHeight + 4
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIcon
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off";
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down";
} else {
return "volume_up";
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
let newVolume
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5)
} else {
newVolume = Math.max(0, currentVolume - 5)
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false
AudioService.sink.audio.volume = newVolume / 100
}
wheelEvent.accepted = true
}
}
return "volume_up";
}
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
DankIcon {
name: "settings"
size: Theme.barIconSize(root.barThickness)
color: root.isActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
}
Row {
id: controlIndicators
visible: !root.isVerticalOrientation
anchors.centerIn: parent
}
spacing: Theme.spacingXS
MouseArea {
id: audioWheelArea
DankIcon {
id: networkIcon
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
let newVolume;
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else {
newVolume = Math.max(0, currentVolume - 5);
name: {
if (NetworkService.wifiToggling) {
return "sync";
}
if (NetworkService.networkStatus === "ethernet") {
return "lan";
}
return NetworkService.wifiSignalIcon;
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false;
AudioService.sink.audio.volume = newVolume / 100;
size: Theme.barIconSize(root.barThickness)
color: {
if (NetworkService.wifiToggling) {
return Theme.primary;
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
}
wheelEvent.accepted = true;
anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
}
DankIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.barIconSize(root.barThickness)
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
Rectangle {
width: audioIcon.implicitWidth + 4
height: audioIcon.implicitHeight + 4
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIcon
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off";
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down";
} else {
return "volume_up";
}
}
return "volume_up";
}
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
id: audioWheelArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
let newVolume;
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else {
newVolume = Math.max(0, currentVolume - 5);
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false;
AudioService.sink.audio.volume = newVolume / 100;
}
wheelEvent.accepted = true;
}
}
}
DankIcon {
name: "mic"
size: Theme.barIconSize(root.barThickness)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: false
}
DankIcon {
name: "settings"
size: Theme.barIconSize(root.barThickness)
color: root.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
}
}
DankIcon {
name: "mic"
size: Theme.barIconSize(barThickness)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: false // TODO: Add mic detection
}
// Fallback settings icon when all other icons are hidden
DankIcon {
name: "settings"
size: Theme.barIconSize(barThickness)
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
}
MouseArea {
id: controlCenterArea
anchors.fill: parent
hoverEnabled: true
x: -root.leftMargin
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
root.clicked();
root.clicked()
}
}
}

View File

@@ -1,37 +1,20 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true
property bool showIcon: true
property var toggleProcessList
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
property var popoutTarget: null
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (cpuContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (cpuColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = cpuArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: {
DgopService.addRef(["cpu"]);
}
@@ -39,120 +22,119 @@ Rectangle {
DgopService.removeRef(["cpu"]);
}
MouseArea {
id: cpuArea
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? cpuColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: cpuColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "memory"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--";
}
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "memory"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--%";
}
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100%"
}
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("cpu");
if (root.toggleProcessList) {
root.toggleProcessList();
}
}
}
Column {
id: cpuColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "memory"
size: Theme.barIconSize(barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--";
}
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "memory"
size: Theme.barIconSize(barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--%";
}
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "100%"
}
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}

View File

@@ -1,37 +1,20 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true
property bool showIcon: true
property var toggleProcessList
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
property var popoutTarget: null
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (cpuTempContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (cpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = cpuTempArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: {
DgopService.addRef(["cpu"]);
}
@@ -39,121 +22,119 @@ Rectangle {
DgopService.removeRef(["cpu"]);
}
MouseArea {
id: cpuTempArea
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuTempContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? cpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: cpuTempColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--";
}
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuTempContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--°";
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100°"
}
width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("cpu");
if (root.toggleProcessList) {
root.toggleProcessList();
}
}
}
Column {
id: cpuTempColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--";
}
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: cpuTempContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--°";
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "100°"
}
width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}

View File

@@ -1,44 +1,36 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property var widgetData: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property bool isHovered: mouseArea.containsMouse
property var selectedMount: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
return null
}
// Force re-evaluation when mountPath changes
const currentMountPath = root.mountPath || "/"
// First try to find exact match
for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === currentMountPath) {
return DgopService.diskMounts[i]
}
}
// Fallback to root
for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === "/") {
return DgopService.diskMounts[i]
}
}
// Last resort - first mount
return DgopService.diskMounts[0] || null
}
@@ -50,17 +42,6 @@ Rectangle {
return parseFloat(percentStr) || 0
}
width: isVertical ? widgetThickness : (diskContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (diskColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent"
}
const baseColor = Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
Component.onCompleted: {
DgopService.addRef(["diskmounts"])
}
@@ -70,7 +51,6 @@ Rectangle {
Connections {
function onWidgetDataChanged() {
// Force property re-evaluation by triggering change detection
root.mountPath = Qt.binding(() => {
return (root.widgetData && root.widgetData.mountPath !== undefined) ? root.widgetData.mountPath : "/"
})
@@ -82,21 +62,18 @@ Rectangle {
const currentMountPath = root.mountPath || "/"
// First try to find exact match
for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === currentMountPath) {
return DgopService.diskMounts[i]
}
}
// Fallback to root
for (let i = 0; i < DgopService.diskMounts.length; i++) {
if (DgopService.diskMounts[i].mount === "/") {
return DgopService.diskMounts[i]
}
}
// Last resort - first mount
return DgopService.diskMounts[0] || null
})
}
@@ -104,6 +81,112 @@ Rectangle {
target: SettingsData
}
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : diskContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? diskColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: diskColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "storage"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--"
}
return root.diskUsagePercent.toFixed(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: diskContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "storage"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (!root.selectedMount) {
return "--"
}
return root.selectedMount.mount
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--%"
}
return root.diskUsagePercent.toFixed(0) + "%"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100%"
}
width: Math.max(diskBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
Loader {
id: tooltipLoader
active: false
@@ -111,11 +194,12 @@ Rectangle {
}
MouseArea {
id: diskArea
id: mouseArea
z: 1
anchors.fill: parent
hoverEnabled: root.isVertical
hoverEnabled: root.isVerticalOrientation
onEntered: {
if (root.isVertical && root.selectedMount) {
if (root.isVerticalOrientation && root.selectedMount) {
tooltipLoader.active = true
if (tooltipLoader.item) {
const globalPos = mapToGlobal(width / 2, height / 2)
@@ -136,107 +220,4 @@ Rectangle {
tooltipLoader.active = false
}
}
Column {
id: diskColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "storage"
size: Theme.barIconSize(barThickness)
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--"
}
return root.diskUsagePercent.toFixed(0)
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: diskContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "storage"
size: Theme.barIconSize(barThickness)
color: {
if (root.diskUsagePercent > 90) {
return Theme.tempDanger
}
if (root.diskUsagePercent > 75) {
return Theme.tempWarning
}
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (!root.selectedMount) {
return "--"
}
return root.selectedMount.mount
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
}
StyledText {
text: {
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
return "--%"
}
return root.diskUsagePercent.toFixed(0) + "%"
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "100%"
}
width: Math.max(diskBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}

View File

@@ -4,25 +4,20 @@ import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Hyprland
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property var parentScreen
property bool compactMode: SettingsData.focusedWindowCompactMode
property int availableWidth: 400
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
readonly property int baseWidth: contentRow.implicitWidth + horizontalPadding * 2
readonly property int maxNormalWidth: 456
readonly property int maxCompactWidth: 288
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property var activeDesktopEntry: null
property bool isHovered: mouseArea.containsMouse
Component.onCompleted: {
updateDesktopEntry()
@@ -74,161 +69,157 @@ Rectangle {
return false
}
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow)
try {
if (!Hyprland.toplevels) return false
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow)
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
return false
}
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
} catch (e) {
console.error("FocusedApp: hasWindowsOnCurrentWorkspace error:", e)
return false
}
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
}
return activeWindow && activeWindow.title
}
width: !hasWindowsOnCurrentWorkspace ? 0 : (isVertical ? widgetThickness : (compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth)))
height: !hasWindowsOnCurrentWorkspace ? 0 : (isVertical ? widgetThickness : widgetThickness)
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (!activeWindow || !activeWindow.title) {
return "transparent";
}
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
clip: true
visible: hasWindowsOnCurrentWorkspace
IconImage {
id: appIcon
anchors.centerIn: parent
width: 18
height: 18
visible: root.isVertical && activeWindow && status === Image.Ready
source: {
if (!activeWindow || !activeWindow.appId) return ""
const moddedId = Paths.moddedAppId(activeWindow.appId)
if (moddedId.toLowerCase().includes("steam_app")) return ""
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
}
smooth: true
mipmap: true
asynchronous: true
}
DankIcon {
anchors.centerIn: parent
size: 18
name: "sports_esports"
color: Theme.surfaceText
visible: {
if (!root.isVertical || !activeWindow || !activeWindow.appId) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return moddedId.toLowerCase().includes("steam_app")
}
}
Text {
anchors.centerIn: parent
visible: {
if (!root.isVertical || !activeWindow || !activeWindow.appId) return false
if (appIcon.status === Image.Ready) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return !moddedId.toLowerCase().includes("steam_app")
}
text: {
if (!activeWindow || !activeWindow.appId) return "?"
if (activeDesktopEntry && activeDesktopEntry.name) {
return activeDesktopEntry.name.charAt(0).toUpperCase()
content: Component {
Item {
implicitWidth: {
if (!root.hasWindowsOnCurrentWorkspace) return 0
if (root.isVerticalOrientation) return root.widgetThickness - root.horizontalPadding * 2
const baseWidth = contentRow.implicitWidth
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2)
}
return activeWindow.appId.charAt(0).toUpperCase()
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
clip: false
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVertical
StyledText {
id: appText
text: {
if (!activeWindow || !activeWindow.appId) {
return "";
IconImage {
id: appIcon
anchors.centerIn: parent
width: 18
height: 18
visible: root.isVerticalOrientation && activeWindow && status === Image.Ready
source: {
if (!activeWindow || !activeWindow.appId) return ""
const moddedId = Paths.moddedAppId(activeWindow.appId)
if (moddedId.toLowerCase().includes("steam_app")) return ""
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
}
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
smooth: true
mipmap: true
asynchronous: true
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 80 : 180)
visible: !compactMode && text.length > 0
}
StyledText {
text: "•"
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !compactMode && appText.text && titleText.text
}
StyledText {
id: titleText
text: {
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
const appName = appText.text;
if (!title || !appName) {
return title;
DankIcon {
anchors.centerIn: parent
size: 18
name: "sports_esports"
color: Theme.surfaceText
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return moddedId.toLowerCase().includes("steam_app")
}
if (title.endsWith(" - " + appName)) {
return title.substring(0, title.length - (" - " + appName).length);
}
if (title.endsWith(appName)) {
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
}
return title;
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 280 : 250)
visible: text.length > 0
}
Text {
anchors.centerIn: parent
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
if (appIcon.status === Image.Ready) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return !moddedId.toLowerCase().includes("steam_app")
}
text: {
if (!activeWindow || !activeWindow.appId) return "?"
if (activeDesktopEntry && activeDesktopEntry.name) {
return activeDesktopEntry.name.charAt(0).toUpperCase()
}
return activeWindow.appId.charAt(0).toUpperCase()
}
font.pixelSize: 10
color: Theme.surfaceText
}
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVerticalOrientation
StyledText {
id: appText
text: {
if (!activeWindow || !activeWindow.appId) {
return "";
}
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 80 : 180)
visible: !compactMode && text.length > 0
}
StyledText {
text: "•"
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: !compactMode && appText.text && titleText.text
}
StyledText {
id: titleText
text: {
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
const appName = appText.text;
if (!title || !appName) {
return title;
}
if (title.endsWith(" - " + appName)) {
return title.substring(0, title.length - (" - " + appName).length);
}
if (title.endsWith(appName)) {
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
}
return title;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, compactMode ? 280 : 250)
visible: text.length > 0
}
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: root.isVertical
hoverEnabled: root.isVerticalOrientation
acceptedButtons: Qt.NoButton
onEntered: {
if (root.isVertical && activeWindow && activeWindow.appId && root.parentScreen) {
if (root.isVerticalOrientation && activeWindow && activeWindow.appId && root.parentScreen) {
tooltipLoader.active = true
if (tooltipLoader.item) {
const globalPos = mapToGlobal(width / 2, height / 2)
@@ -260,14 +251,4 @@ Rectangle {
active: false
sourceComponent: DankTooltip {}
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -1,26 +1,20 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true
property bool showIcon: true
property var toggleProcessList
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property var popoutTarget: null
property var widgetData: null
property real barThickness: 48
property real widgetThickness: 30
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property real displayTemp: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return 0;
@@ -34,7 +28,6 @@ Rectangle {
}
function updateWidgetPciId(pciId) {
// Find and update this widget's pciId in the settings
const sections = ["left", "center", "right"];
for (let s = 0; s < sections.length; s++) {
const sectionId = sections[s];
@@ -68,17 +61,6 @@ Rectangle {
}
}
width: isVertical ? widgetThickness : (gpuTempContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (gpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = gpuArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: {
DgopService.addRef(["gpu"]);
if (widgetData && widgetData.pciId) {
@@ -92,12 +74,10 @@ Rectangle {
if (widgetData && widgetData.pciId) {
DgopService.removeGpuPciId(widgetData.pciId);
}
}
Connections {
function onWidgetDataChanged() {
// Force property re-evaluation by triggering change detection
root.selectedGpuIndex = Qt.binding(() => {
return (root.widgetData && root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0;
});
@@ -106,122 +86,122 @@ Rectangle {
target: SettingsData
}
MouseArea {
id: gpuArea
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : gpuTempContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? gpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: gpuTempColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--";
}
return Math.round(root.displayTemp).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: gpuTempContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--°";
}
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100°"
}
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("cpu");
if (root.toggleProcessList) {
root.toggleProcessList();
}
}
}
Column {
id: gpuTempColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--";
}
return Math.round(root.displayTemp).toString();
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: gpuTempContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--°";
}
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "100°"
}
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
Timer {
id: autoSaveTimer
@@ -231,13 +211,10 @@ Rectangle {
if (DgopService.availableGpus && DgopService.availableGpus.length > 0) {
const firstGpu = DgopService.availableGpus[0];
if (firstGpu && firstGpu.pciId) {
// Save the first GPU's PCI ID to this widget's settings
updateWidgetPciId(firstGpu.pciId);
DgopService.addGpuPciId(firstGpu.pciId);
}
}
}
}
}

View File

@@ -2,51 +2,33 @@ import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
width: isVertical ? widgetThickness : (idleIcon.width + horizontalPadding * 2)
height: isVertical ? (idleIcon.height + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
DankIcon {
anchors.centerIn: parent
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.surfaceText
}
}
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
id: idleIcon
anchors.centerIn: parent
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
size: Theme.barIconSize(barThickness, -4)
color: Theme.surfaceText
}
MouseArea {
id: mouseArea
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
SessionService.toggleIdleInhibit();
SessionService.toggleIdleInhibit()
}
}
}

View File

@@ -3,38 +3,69 @@ import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modules.Plugins
import qs.Modules.ProcessList
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property string currentLayout: ""
property string hyprlandKeyboard: ""
width: isVertical ? widgetThickness : (contentRow.implicitWidth + horizontalPadding * 2)
height: isVertical ? (contentColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : contentRow.implicitWidth
implicitHeight: root.isVerticalOrientation ? contentColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
Column {
id: contentColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "keyboard"
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (!root.currentLayout) return ""
const parts = root.currentLayout.split(" ")
if (parts.length > 0) {
return parts[0].substring(0, 2).toUpperCase()
}
return root.currentLayout.substring(0, 2).toUpperCase()
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: contentRow
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: Theme.spacingS
StyledText {
text: root.currentLayout
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
MouseArea {
id: mouseArea
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (CompositorService.isNiri) {
@@ -51,53 +82,6 @@ Rectangle {
}
}
Column {
id: contentColumn
anchors.centerIn: parent
spacing: 1
visible: root.isVertical
DankIcon {
name: "keyboard"
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (!currentLayout) return ""
const parts = currentLayout.split(" ")
if (parts.length > 0) {
return parts[0].substring(0, 2).toUpperCase()
}
return currentLayout.substring(0, 2).toUpperCase()
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVertical
StyledText {
text: currentLayout
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Timer {
id: updateTimer
interval: 1000

View File

@@ -3,124 +3,94 @@ import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Item {
BasePill {
id: root
property bool isActive: false
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "left"
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))
property var hyprlandOverviewLoader: null
signal clicked()
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
width: widgetThickness
height: widgetThickness
MouseArea {
id: launcherArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: function (mouse){
if (mouse.button === Qt.RightButton) {
if (CompositorService.isNiri) {
NiriService.toggleOverview()
}
return
DankIcon {
visible: SettingsData.launcherLogoMode === "apps"
anchors.centerIn: parent
name: "apps"
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.surfaceText
}
root.clicked();
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);
SystemLogo {
visible: SettingsData.launcherLogoMode === "os"
anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
colorOverride: Theme.effectiveLogoColor
brightnessOverride: SettingsData.launcherLogoBrightness
contrastOverride: SettingsData.launcherLogoContrast
}
IconImage {
visible: SettingsData.launcherLogoMode === "compositor"
anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: {
if (CompositorService.isNiri) {
return "file://" + Theme.shellDir + "/assets/niri.svg"
} else if (CompositorService.isHyprland) {
return "file://" + Theme.shellDir + "/assets/hyprland.svg"
}
return ""
}
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
IconImage {
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
}
}
Rectangle {
id: launcherContent
MouseArea {
id: customMouseArea
anchors.fill: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = launcherArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
visible: SettingsData.launcherLogoMode === "apps"
anchors.centerIn: parent
name: "apps"
size: Theme.barIconSize(barThickness, -4)
color: Theme.surfaceText
}
SystemLogo {
visible: SettingsData.launcherLogoMode === "os"
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
colorOverride: Theme.effectiveLogoColor
brightnessOverride: SettingsData.launcherLogoBrightness
contrastOverride: SettingsData.launcherLogoContrast
}
IconImage {
visible: SettingsData.launcherLogoMode === "compositor"
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: {
if (CompositorService.isNiri) {
return "file://" + Theme.shellDir + "/assets/niri.svg"
} else if (CompositorService.isHyprland) {
return "file://" + Theme.shellDir + "/assets/hyprland.svg"
}
return ""
}
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
IconImage {
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.RightButton
onPressed: function (mouse){
if (CompositorService.isNiri) {
NiriService.toggleOverview()
} else if (root.hyprlandOverviewLoader?.item) {
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
}
}
}

View File

@@ -1,412 +1,328 @@
import QtQuick
import Quickshell.Services.Mpris
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property bool playerAvailable: activePlayer !== null
property bool compactMode: false
readonly property int textWidth: {
switch (SettingsData.mediaSize) {
case 0:
return 0; // No text in small mode
return 0;
case 2:
return 180; // Large text area
return 180;
default:
return 120; // Medium text area
return 120;
}
}
readonly property int currentContentWidth: {
if (isVertical) {
return widgetThickness;
if (isVerticalOrientation) {
return widgetThickness - horizontalPadding * 2;
}
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
const audioVizWidth = 20;
const contentWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0) + horizontalPadding * 2;
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0);
}
readonly property int currentContentHeight: {
if (!isVertical) {
return widgetThickness;
if (!isVerticalOrientation) {
return widgetThickness - horizontalPadding * 2;
}
const audioVizHeight = 20;
const playButtonHeight = 24;
return audioVizHeight + Theme.spacingXS + playButtonHeight + horizontalPadding * 2;
return audioVizHeight + Theme.spacingXS + playButtonHeight;
}
property string section: "center"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
signal clicked()
width: currentContentWidth
height: currentContentHeight
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
states: [
State {
name: "shown"
when: playerAvailable
PropertyChanges {
target: root
opacity: 1
width: currentContentWidth
height: currentContentHeight
}
},
State {
name: "hidden"
when: !playerAvailable
PropertyChanges {
target: root
opacity: 0
width: isVertical ? widgetThickness : 0
height: isVertical ? 0 : widgetThickness
}
}
]
transitions: [
Transition {
from: "shown"
to: "hidden"
SequentialAnimation {
PauseAnimation {
duration: 500
}
content: Component {
Item {
implicitWidth: root.playerAvailable ? root.currentContentWidth : 0
implicitHeight: root.playerAvailable ? root.currentContentHeight : 0
opacity: root.playerAvailable ? 1 : 0
Behavior on opacity {
NumberAnimation {
properties: isVertical ? "opacity,height" : "opacity,width"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
},
Transition {
from: "hidden"
to: "shown"
NumberAnimation {
properties: isVertical ? "opacity,height" : "opacity,width"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
]
Column {
id: verticalLayout
visible: root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
AudioVisualization {
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
const globalPos = parent.mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, parent.width)
root.popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
}
root.clicked()
Behavior on implicitWidth {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Rectangle {
width: 24
height: 24
radius: 12
anchors.horizontalCenter: parent.horizontalCenter
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
visible: root.playerAvailable
opacity: activePlayer ? 1 : 0.3
Behavior on implicitHeight {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
DankIcon {
Column {
id: verticalLayout
visible: root.isVerticalOrientation
anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
size: 14
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
}
spacing: Theme.spacingXS
MouseArea {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: (mouse) => {
if (!activePlayer) return
if (mouse.button === Qt.LeftButton) {
activePlayer.togglePlaying()
} else if (mouse.button === Qt.MiddleButton) {
activePlayer.previous()
} else if (mouse.button === Qt.RightButton) {
activePlayer.next()
AudioVisualization {
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = parent.mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, parent.width)
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
}
root.clicked()
}
}
}
Rectangle {
width: 24
height: 24
radius: 12
anchors.horizontalCenter: parent.horizontalCenter
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
visible: root.playerAvailable
opacity: activePlayer ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
size: 14
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
}
MouseArea {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: (mouse) => {
if (!activePlayer) return
if (mouse.button === Qt.LeftButton) {
activePlayer.togglePlaying()
} else if (mouse.button === Qt.MiddleButton) {
activePlayer.previous()
} else if (mouse.button === Qt.RightButton) {
activePlayer.next()
}
}
}
}
}
}
}
Row {
id: mediaRow
Row {
id: mediaRow
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
Row {
id: mediaInfo
spacing: Theme.spacingXS
Row {
id: mediaInfo
spacing: Theme.spacingXS
AudioVisualization {
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: textContainer
property string displayText: {
if (!activePlayer || !activePlayer.trackTitle) {
return "";
AudioVisualization {
anchors.verticalCenter: parent.verticalCenter
}
let identity = activePlayer.identity || "";
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
let title = "";
let subtitle = "";
if (isWebMedia && activePlayer.trackTitle) {
title = activePlayer.trackTitle;
subtitle = activePlayer.trackArtist || identity;
} else {
title = activePlayer.trackTitle || "Unknown Track";
subtitle = activePlayer.trackArtist || "";
Rectangle {
id: textContainer
property string displayText: {
if (!activePlayer || !activePlayer.trackTitle) {
return "";
}
let identity = activePlayer.identity || "";
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
let title = "";
let subtitle = "";
if (isWebMedia && activePlayer.trackTitle) {
title = activePlayer.trackTitle;
subtitle = activePlayer.trackArtist || identity;
} else {
title = activePlayer.trackTitle || "Unknown Track";
subtitle = activePlayer.trackArtist || "";
}
return subtitle.length > 0 ? title + " • " + subtitle : title;
}
anchors.verticalCenter: parent.verticalCenter
width: textWidth
height: root.widgetThickness
visible: SettingsData.mediaSize > 0
clip: true
color: "transparent"
StyledText {
id: mediaText
property bool needsScrolling: implicitWidth > textContainer.width
property real scrollOffset: 0
anchors.verticalCenter: parent.verticalCenter
text: textContainer.displayText
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
wrapMode: Text.NoWrap
x: needsScrolling ? -scrollOffset : 0
onTextChanged: {
scrollOffset = 0;
scrollAnimation.restart();
}
SequentialAnimation {
id: scrollAnimation
running: mediaText.needsScrolling && textContainer.visible
loops: Animation.Infinite
PauseAnimation {
duration: 2000
}
NumberAnimation {
target: mediaText
property: "scrollOffset"
from: 0
to: mediaText.implicitWidth - textContainer.width + 5
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
easing.type: Easing.Linear
}
PauseAnimation {
duration: 2000
}
NumberAnimation {
target: mediaText
property: "scrollOffset"
to: 0
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
easing.type: Easing.Linear
}
}
}
MouseArea {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onPressed: {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.width)
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
}
root.clicked()
}
}
}
return subtitle.length > 0 ? title + " • " + subtitle : title;
}
anchors.verticalCenter: parent.verticalCenter
width: textWidth
height: 20
visible: SettingsData.mediaSize > 0
clip: true
color: "transparent"
StyledText {
id: mediaText
property bool needsScrolling: implicitWidth > textContainer.width
property real scrollOffset: 0
Row {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
text: textContainer.displayText
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
wrapMode: Text.NoWrap
x: needsScrolling ? -scrollOffset : 0
onTextChanged: {
scrollOffset = 0;
scrollAnimation.restart();
Rectangle {
width: 20
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? Theme.primaryHover : "transparent"
visible: root.playerAvailable
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: "skip_previous"
size: 12
color: Theme.surfaceText
}
MouseArea {
id: prevArea
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.previous();
}
}
}
}
SequentialAnimation {
id: scrollAnimation
Rectangle {
width: 24
height: 24
radius: 12
anchors.verticalCenter: parent.verticalCenter
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
visible: root.playerAvailable
opacity: activePlayer ? 1 : 0.3
running: mediaText.needsScrolling && textContainer.visible
loops: Animation.Infinite
PauseAnimation {
duration: 2000
DankIcon {
anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
size: 14
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
}
NumberAnimation {
target: mediaText
property: "scrollOffset"
from: 0
to: mediaText.implicitWidth - textContainer.width + 5
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
easing.type: Easing.Linear
MouseArea {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.togglePlaying();
}
}
}
PauseAnimation {
duration: 2000
}
NumberAnimation {
target: mediaText
property: "scrollOffset"
to: 0
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
easing.type: Easing.Linear
}
}
}
Rectangle {
width: 20
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? Theme.primaryHover : "transparent"
visible: playerAvailable
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
MouseArea {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onPressed: {
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.width)
root.popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
DankIcon {
anchors.centerIn: parent
name: "skip_next"
size: 12
color: Theme.surfaceText
}
MouseArea {
id: nextArea
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.next();
}
}
}
root.clicked()
}
}
}
}
Row {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: 20
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? Theme.primaryHover : "transparent"
visible: root.playerAvailable
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: "skip_previous"
size: 12
color: Theme.surfaceText
}
MouseArea {
id: prevArea
anchors.fill: parent
enabled: root.playerAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.previous();
}
}
}
}
Rectangle {
width: 24
height: 24
radius: 12
anchors.verticalCenter: parent.verticalCenter
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
visible: root.playerAvailable
opacity: activePlayer ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
size: 14
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
}
MouseArea {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.togglePlaying();
}
}
}
}
Rectangle {
width: 20
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? Theme.primaryHover : "transparent"
visible: playerAvailable
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: "skip_next"
size: 12
color: Theme.surfaceText
}
MouseArea {
id: nextArea
anchors.fill: parent
enabled: root.playerAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.next();
}
}
}
}
}
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -1,194 +1,161 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Modules.ProcessList
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property int availableWidth: 400
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
readonly property int maxNormalWidth: 456
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
function formatNetworkSpeed(bytesPerSec) {
if (bytesPerSec < 1024) {
return bytesPerSec.toFixed(0) + " B/s";
return bytesPerSec.toFixed(0) + " B/s"
} else if (bytesPerSec < 1024 * 1024) {
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
} else if (bytesPerSec < 1024 * 1024 * 1024) {
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
} else {
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
}
}
width: isVertical ? widgetThickness : (contentRow.implicitWidth + horizontalPadding * 2)
height: isVertical ? (contentColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = networkArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: {
DgopService.addRef(["network"]);
DgopService.addRef(["network"])
}
Component.onDestruction: {
DgopService.removeRef(["network"]);
DgopService.removeRef(["network"])
}
MouseArea {
id: networkArea
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : contentRow.implicitWidth
implicitHeight: root.isVerticalOrientation ? contentColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
Column {
id: contentColumn
anchors.centerIn: parent
spacing: 2
visible: root.isVerticalOrientation
Column {
id: contentColumn
anchors.centerIn: parent
spacing: 2
visible: root.isVertical
DankIcon {
name: "network_check"
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const rate = DgopService.networkRxRate
if (rate < 1024) return rate.toFixed(0)
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.info
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const rate = DgopService.networkTxRate
if (rate < 1024) return rate.toFixed(0)
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVertical
DankIcon {
name: "network_check"
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
StyledText {
text: "↓"
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.info
}
StyledText {
text: DgopService.networkRxRate > 0 ? formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
wrapMode: Text.NoWrap
StyledTextMetrics {
id: rxBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "88.8 MB/s"
DankIcon {
name: "network_check"
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
width: Math.max(rxBaseline.width, paintedWidth)
StyledText {
text: {
const rate = DgopService.networkRxRate
if (rate < 1024) return rate.toFixed(0)
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.info
anchors.horizontalCenter: parent.horizontalCenter
}
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
StyledText {
text: {
const rate = DgopService.networkTxRate
if (rate < 1024) return rate.toFixed(0)
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: contentRow
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.isVerticalOrientation
DankIcon {
name: "network_check"
size: Theme.barIconSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
StyledText {
text: "↓"
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.info
}
StyledText {
text: DgopService.networkRxRate > 0 ? root.formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
wrapMode: Text.NoWrap
StyledTextMetrics {
id: rxBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "88.8 MB/s"
}
width: Math.max(rxBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
StyledText {
text: "↑"
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.error
}
StyledText {
text: DgopService.networkTxRate > 0 ? root.formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
wrapMode: Text.NoWrap
StyledTextMetrics {
id: txBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "88.8 MB/s"
}
width: Math.max(txBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
StyledText {
text: "↑"
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.error
}
StyledText {
text: DgopService.networkTxRate > 0 ? formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
wrapMode: Text.NoWrap
StyledTextMetrics {
id: txBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "88.8 MB/s"
}
width: Math.max(txBaseline.width, paintedWidth)
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}

View File

@@ -1,22 +1,13 @@
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "right"
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()
readonly property string focusedScreenName: (
CompositorService.isHyprland && typeof Hyprland !== "undefined" && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor ? (Hyprland.focusedWorkspace.monitor.name || "") :
CompositorService.isNiri && typeof NiriService !== "undefined" && NiriService.currentOutput ? NiriService.currentOutput : ""
@@ -43,54 +34,30 @@ Rectangle {
readonly property var notepadInstance: resolveNotepadInstance()
readonly property bool isActive: notepadInstance?.isVisible ?? false
width: isVertical ? widgetThickness : (notepadIcon.width + horizontalPadding * 2)
height: isVertical ? (notepadIcon.height + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
DankIcon {
id: notepadIcon
anchors.centerIn: parent
name: "assignment"
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.surfaceText
}
}
const baseColor = notepadArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
id: notepadIcon
anchors.centerIn: parent
name: "assignment"
size: Theme.barIconSize(barThickness, -4)
color: notepadArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.primary
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 4
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 4
visible: NotepadStorageService.tabs && NotepadStorageService.tabs.length > 0
opacity: 0.8
}
MouseArea {
id: notepadArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
const inst = root.notepadInstance
if (inst) {
inst.toggle()
}
root.clicked()
}
}
}

View File

@@ -1,76 +1,36 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Widgets
Item {
BasePill {
id: root
property bool hasUnread: false
property bool isActive: false
property bool isVertical: axis?.isVertical ?? false
property var axis: null
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 clicked()
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
width: widgetThickness
height: widgetThickness
MouseArea {
id: notificationArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
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)
}
root.clicked()
}
}
Rectangle {
id: notificationContent
anchors.fill: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent"
DankIcon {
id: notifIcon
anchors.centerIn: parent
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
size: Theme.barIconSize(root.barThickness, -4)
color: SessionData.doNotDisturb ? Theme.error : (root.isActive ? Theme.primary : Theme.surfaceText)
}
const baseColor = notificationArea.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: SessionData.doNotDisturb ? "notifications_off" : "notifications"
size: Theme.barIconSize(barThickness, -4)
color: SessionData.doNotDisturb ? Theme.error : (notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText)
}
Rectangle {
width: 8
height: 8
radius: 4
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 6
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 6
visible: root.hasUnread
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: notifIcon.right
anchors.top: notifIcon.top
visible: root.hasUnread
}
}
}
}

View File

@@ -4,7 +4,7 @@ import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
Item {
id: root
property bool isVertical: axis?.isVertical ?? false
@@ -19,26 +19,167 @@ Rectangle {
readonly property int activeCount: PrivacyService.microphoneActive + PrivacyService.cameraActive + PrivacyService.screensharingActive
readonly property real contentWidth: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0
readonly property real contentHeight: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0
readonly property real visualWidth: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0)
readonly property real visualHeight: isVertical ? (hasActivePrivacy ? (contentHeight + horizontalPadding * 2) : 0) : widgetThickness
width: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0)
height: isVertical ? (hasActivePrivacy ? (contentHeight + horizontalPadding * 2) : 0) : (hasActivePrivacy ? widgetThickness : 0)
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness
visible: hasActivePrivacy
opacity: hasActivePrivacy ? 1 : 0
enabled: hasActivePrivacy
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
Rectangle {
id: visualContent
width: root.visualWidth
height: root.visualHeight
anchors.centerIn: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent"
}
return Qt.rgba(privacyArea.containsMouse ? Theme.errorPressed.r : Theme.errorHover.r, privacyArea.containsMouse ? Theme.errorPressed.g : Theme.errorHover.g, privacyArea.containsMouse ? Theme.errorPressed.b : Theme.errorHover.b, (privacyArea.containsMouse ? Theme.errorPressed.a : Theme.errorHover.a) * Theme.widgetTransparency)
}
return Qt.rgba(privacyArea.containsMouse ? Theme.errorPressed.r : Theme.errorHover.r, privacyArea.containsMouse ? Theme.errorPressed.g : Theme.errorHover.g, privacyArea.containsMouse ? Theme.errorPressed.b : Theme.errorHover.b, (privacyArea.containsMouse ? Theme.errorPressed.a : Theme.errorHover.a) * Theme.widgetTransparency);
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: root.isVertical && root.hasActivePrivacy
Item {
width: 18
height: 18
visible: PrivacyService.microphoneActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: {
const sourceAudio = AudioService.source?.audio
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0
if (muted) return "mic_off"
return "mic"
}
size: Theme.iconSizeSmall
color: Theme.error
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: PrivacyService.cameraActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: Theme.surfaceText
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
}
}
Item {
width: 18
height: 18
visible: PrivacyService.screensharingActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: Theme.warning
filled: true
anchors.centerIn: parent
}
}
}
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVertical && root.hasActivePrivacy
Item {
width: 18
height: 18
visible: PrivacyService.microphoneActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: {
const sourceAudio = AudioService.source?.audio
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0
if (muted) return "mic_off"
return "mic"
}
size: Theme.iconSizeSmall
color: Theme.error
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: PrivacyService.cameraActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: Theme.surfaceText
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
}
}
Item {
width: 18
height: 18
visible: PrivacyService.screensharingActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: Theme.warning
filled: true
anchors.centerIn: parent
}
}
}
}
MouseArea {
// Privacy indicator click handler
id: privacyArea
z: -1
anchors.fill: parent
hoverEnabled: hasActivePrivacy
enabled: hasActivePrivacy
@@ -47,141 +188,8 @@ Rectangle {
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: root.isVertical && hasActivePrivacy
Item {
width: 18
height: 18
visible: PrivacyService.microphoneActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "mic"
size: Theme.iconSizeSmall
color: Theme.error
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: PrivacyService.cameraActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: Theme.surfaceText
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
}
}
Item {
width: 18
height: 18
visible: PrivacyService.screensharingActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: Theme.warning
filled: true
anchors.centerIn: parent
}
}
}
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVertical && hasActivePrivacy
Item {
width: 18
height: 18
visible: PrivacyService.microphoneActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "mic"
size: Theme.iconSizeSmall
color: Theme.error
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: PrivacyService.cameraActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: Theme.surfaceText
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
}
}
Item {
width: 18
height: 18
visible: PrivacyService.screensharingActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: Theme.warning
filled: true
anchors.centerIn: parent
}
}
}
Rectangle {
id: tooltip
width: tooltipText.contentWidth + Theme.spacingM * 2
height: tooltipText.contentHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
@@ -196,7 +204,6 @@ Rectangle {
StyledText {
id: tooltipText
anchors.centerIn: parent
text: PrivacyService.getPrivacySummary()
font.pixelSize: Theme.barTextSize(barThickness)
@@ -222,9 +229,7 @@ Rectangle {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Behavior on width {
@@ -234,7 +239,6 @@ Rectangle {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on height {
@@ -244,7 +248,5 @@ Rectangle {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}

View File

@@ -1,37 +1,19 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool showPercentage: true
property bool showIcon: true
property var toggleProcessList
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
property var popoutTarget: null
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
width: isVertical ? widgetThickness : (ramContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (ramColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = ramArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Component.onCompleted: {
DgopService.addRef(["memory"]);
@@ -40,120 +22,119 @@ Rectangle {
DgopService.removeRef(["memory"]);
}
MouseArea {
id: ramArea
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : ramContent.implicitWidth
implicitHeight: root.isVerticalOrientation ? ramColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
id: ramColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "developer_board"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.memoryUsage > 90) {
return Theme.tempDanger;
}
if (DgopService.memoryUsage > 75) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--";
}
return DgopService.memoryUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: ramContent
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "developer_board"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.memoryUsage > 90) {
return Theme.tempDanger;
}
if (DgopService.memoryUsage > 75) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--%";
}
return DgopService.memoryUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
text: "100%"
}
width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("memory");
if (root.toggleProcessList) {
root.toggleProcessList();
}
}
}
Column {
id: ramColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: "developer_board"
size: Theme.barIconSize(barThickness)
color: {
if (DgopService.memoryUsage > 90) {
return Theme.tempDanger;
}
if (DgopService.memoryUsage > 75) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--";
}
return DgopService.memoryUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Row {
id: ramContent
visible: !root.isVertical
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "developer_board"
size: Theme.barIconSize(barThickness)
color: {
if (DgopService.memoryUsage > 90) {
return Theme.tempDanger;
}
if (DgopService.memoryUsage > 75) {
return Theme.tempWarning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--%";
}
return DgopService.memoryUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledTextMetrics {
id: ramBaseline
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
text: "100%"
}
width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ import Quickshell.Widgets
import qs.Common
import qs.Widgets
Rectangle {
Item {
id: root
property bool isVertical: axis?.isVertical ?? false
@@ -15,26 +15,49 @@ Rectangle {
property var parentWindow: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property bool isAtBottom: false
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
readonly property int calculatedSize: SystemTray.items.values.length > 0 ? SystemTray.items.values.length * 24 + horizontalPadding * 2 : 0
width: isVertical ? widgetThickness : calculatedSize
height: isVertical ? calculatedSize : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SystemTray.items.values.length === 0) {
return "transparent";
}
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
readonly property var hiddenTrayIds: {
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || ""
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : []
}
readonly property var visibleTrayItems: {
if (!hiddenTrayIds.length) {
return SystemTray.items.values
}
return SystemTray.items.values.filter(item => {
const itemId = item?.id || ""
return !hiddenTrayIds.includes(itemId.toLowerCase())
})
}
readonly property int calculatedSize: visibleTrayItems.length > 0 ? visibleTrayItems.length * 24 + horizontalPadding * 2 : 0
readonly property real visualWidth: isVertical ? widgetThickness : calculatedSize
readonly property real visualHeight: isVertical ? calculatedSize : widgetThickness
width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness
visible: visibleTrayItems.length > 0
Rectangle {
id: visualBackground
width: root.visualWidth
height: root.visualHeight
anchors.centerIn: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (visibleTrayItems.length === 0) {
return "transparent";
}
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
}
visible: SystemTray.items.values.length > 0
Loader {
id: layoutLoader
@@ -48,84 +71,83 @@ Rectangle {
spacing: 0
Repeater {
model: SystemTray.items.values
model: root.visibleTrayItems
delegate: Item {
property var trayItem: modelData
property string iconSource: {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "") {
return "";
}
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2) {
return icon;
id: delegateRoot
property var trayItem: modelData
property string iconSource: {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "") {
return "";
}
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2) {
return icon;
}
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
return `file://${path}/${fileName}`;
if (icon.startsWith("/") && !icon.startsWith("file://")) {
return `file://${icon}`;
}
return icon;
}
if (icon.startsWith("/") && !icon.startsWith("file://")) {
return `file://${icon}`;
}
return icon;
return "";
}
return "";
}
width: 24
height: 24
width: 24
height: root.barThickness
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
Rectangle {
id: visualContent
width: 24
height: 24
anchors.centerIn: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
}
IconImage {
anchors.centerIn: parent
width: 16
height: 16
source: parent.iconSource
asynchronous: true
smooth: true
mipmap: true
}
MouseArea {
id: trayItemArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!trayItem) {
return;
IconImage {
anchors.centerIn: parent
width: 16
height: 16
source: delegateRoot.iconSource
asynchronous: true
smooth: true
mipmap: true
}
}
if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
trayItem.activate();
return ;
}
if (trayItem.hasMenu) {
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
MouseArea {
id: trayItemArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!delegateRoot.trayItem) {
return;
}
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
delegateRoot.trayItem.activate();
return ;
}
if (delegateRoot.trayItem.hasMenu) {
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
}
}
}
}
}
}
}
}
@@ -135,79 +157,83 @@ Rectangle {
spacing: 0
Repeater {
model: SystemTray.items.values
model: root.visibleTrayItems
delegate: Item {
property var trayItem: modelData
property string iconSource: {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "") {
return "";
id: delegateRoot
property var trayItem: modelData
property string iconSource: {
let icon = trayItem && trayItem.icon;
if (typeof icon === 'string' || icon instanceof String) {
if (icon === "") {
return "";
}
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2) {
return icon;
}
const name = split[0];
const path = split[1];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://")) {
return `file://${icon}`;
}
return icon;
}
if (icon.includes("?path=")) {
const split = icon.split("?path=");
if (split.length !== 2) {
return icon;
return "";
}
width: root.barThickness
height: 24
Rectangle {
id: visualContent
width: 24
height: 24
anchors.centerIn: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
IconImage {
anchors.centerIn: parent
width: 16
height: 16
source: delegateRoot.iconSource
asynchronous: true
smooth: true
mipmap: true
}
}
MouseArea {
id: trayItemArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!delegateRoot.trayItem) {
return;
}
const name = split[0];
const path = split[1];
const fileName = name.substring(name.lastIndexOf("/") + 1);
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://")) {
return `file://${icon}`;
}
return icon;
}
return "";
}
width: 24
height: 24
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
}
IconImage {
anchors.centerIn: parent
width: 16
height: 16
source: parent.iconSource
asynchronous: true
smooth: true
mipmap: true
}
MouseArea {
id: trayItemArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!trayItem) {
return;
}
if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
trayItem.activate();
return ;
}
if (trayItem.hasMenu) {
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
delegateRoot.trayItem.activate();
return ;
}
if (delegateRoot.trayItem.hasMenu) {
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
}
}
}
}
}
}
}
}
@@ -458,7 +484,6 @@ Rectangle {
MouseArea {
id: backArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: menuRoot.goBack()
}
@@ -494,7 +519,6 @@ Rectangle {
id: itemArea
anchors.fill: parent
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {

View File

@@ -1,158 +1,136 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool isActive: 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))
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
readonly property bool isChecking: SystemUpdateService.isChecking
signal clicked()
Ref {
service: SystemUpdateService
}
width: isVertical ? widgetThickness : (updaterIcon.width + horizontalPadding * 2)
height: isVertical ? widgetThickness : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : updaterIcon.implicitWidth
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
const baseColor = updaterArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
id: statusIcon
anchors.centerIn: parent
visible: root.isVertical
name: {
if (isChecking) return "refresh";
if (SystemUpdateService.hasError) return "error";
if (hasUpdates) return "system_update_alt";
return "check_circle";
}
size: Theme.barIconSize(barThickness, -4)
color: {
if (SystemUpdateService.hasError) return Theme.error;
if (hasUpdates) return Theme.primary;
return (updaterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText);
}
RotationAnimation {
id: rotationAnimation
target: statusIcon
property: "rotation"
from: 0
to: 360
duration: 1000
running: isChecking
loops: Animation.Infinite
onRunningChanged: {
if (!running) {
statusIcon.rotation = 0
DankIcon {
id: statusIcon
anchors.centerIn: parent
visible: root.isVerticalOrientation
name: {
if (root.isChecking) return "refresh"
if (SystemUpdateService.hasError) return "error"
if (root.hasUpdates) return "system_update_alt"
return "check_circle"
}
size: Theme.barIconSize(root.barThickness, -4)
color: {
if (SystemUpdateService.hasError) return Theme.error
if (root.hasUpdates) return Theme.primary
return root.isActive ? Theme.primary : Theme.surfaceText
}
}
}
}
Rectangle {
width: 8
height: 8
radius: 4
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 6
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 6
visible: root.isVertical && root.hasUpdates && !root.isChecking
}
RotationAnimation {
id: rotationAnimation
target: statusIcon
property: "rotation"
from: 0
to: 360
duration: 1000
running: root.isChecking
loops: Animation.Infinite
Row {
id: updaterIcon
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVertical
DankIcon {
id: statusIconHorizontal
anchors.verticalCenter: parent.verticalCenter
name: {
if (isChecking) return "refresh";
if (SystemUpdateService.hasError) return "error";
if (hasUpdates) return "system_update_alt";
return "check_circle";
}
size: Theme.barIconSize(barThickness, -4)
color: {
if (SystemUpdateService.hasError) return Theme.error;
if (hasUpdates) return Theme.primary;
return (updaterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText);
}
RotationAnimation {
id: rotationAnimationHorizontal
target: statusIconHorizontal
property: "rotation"
from: 0
to: 360
duration: 1000
running: isChecking
loops: Animation.Infinite
onRunningChanged: {
if (!running) {
statusIconHorizontal.rotation = 0
onRunningChanged: {
if (!running) {
statusIcon.rotation = 0
}
}
}
}
}
StyledText {
id: countText
Rectangle {
width: 8
height: 8
radius: 4
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 6
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 6
visible: root.isVerticalOrientation && root.hasUpdates && !root.isChecking
}
anchors.verticalCenter: parent.verticalCenter
text: SystemUpdateService.updateCount.toString()
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
visible: hasUpdates && !isChecking
Row {
id: updaterIcon
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVerticalOrientation
DankIcon {
id: statusIconHorizontal
anchors.verticalCenter: parent.verticalCenter
name: {
if (root.isChecking) return "refresh"
if (SystemUpdateService.hasError) return "error"
if (root.hasUpdates) return "system_update_alt"
return "check_circle"
}
size: Theme.barIconSize(root.barThickness, -4)
color: {
if (SystemUpdateService.hasError) return Theme.error
if (root.hasUpdates) return Theme.primary
return root.isActive ? Theme.primary : Theme.surfaceText
}
RotationAnimation {
id: rotationAnimationHorizontal
target: statusIconHorizontal
property: "rotation"
from: 0
to: 360
duration: 1000
running: root.isChecking
loops: Animation.Infinite
onRunningChanged: {
if (!running) {
statusIconHorizontal.rotation = 0
}
}
}
}
StyledText {
id: countText
anchors.verticalCenter: parent.verticalCenter
text: SystemUpdateService.updateCount.toString()
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
visible: root.hasUpdates && !root.isChecking
}
}
}
}
MouseArea {
id: updaterArea
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
root.clicked();
root.clicked()
}
}
}
}

View File

@@ -1,46 +1,36 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property int widgetThickness: 28
property int barThickness: 32
property string section: "right"
property var popupTarget: null
property var parentScreen: null
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
property var popoutTarget: null
property bool isHovered: clickArea.containsMouse
signal toggleVpnPopup()
width: isVertical ? widgetThickness : (Theme.iconSize + horizontalPadding * 2)
height: isVertical ? (Theme.iconSize + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
content: Component {
Item {
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
DankIcon {
id: icon
name: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
size: Theme.barIconSize(root.barThickness, -4)
color: DMSNetworkService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
}
const baseColor = clickArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
DankIcon {
id: icon
name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
size: Theme.barIconSize(barThickness, -4)
color: VpnService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
Loader {
@@ -55,24 +45,25 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.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)
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
root.toggleVpnPopup();
}
onEntered: {
if (root.parentScreen && !(popupTarget && popupTarget.shouldBeVisible)) {
if (root.parentScreen && !(popoutTarget && popoutTarget.shouldBeVisible)) {
tooltipLoader.active = true
if (tooltipLoader.item) {
let tooltipText = ""
if (!VpnService.connected) {
if (!DMSNetworkService.connected) {
tooltipText = "VPN Disconnected"
} else {
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1) {
tooltipText = "VPN Connected • " + (names[0] || "")
} else {
@@ -80,7 +71,7 @@ Rectangle {
}
}
if (root.isVertical) {
if (root.isVerticalOrientation) {
const globalPos = mapToGlobal(width / 2, height / 2)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const screenY = root.parentScreen ? root.parentScreen.y : 0
@@ -103,5 +94,4 @@ Rectangle {
tooltipLoader.active = false
}
}
}

View File

@@ -1,120 +1,81 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Rectangle {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "center"
property var popupTarget: null
property var parentScreen: null
property real barThickness: 48
property real widgetThickness: 30
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
signal clicked()
visible: SettingsData.weatherEnabled
width: isVertical ? widgetThickness : (visible ? Math.min(100, weatherRow.implicitWidth + horizontalPadding * 2) : 0)
height: isVertical ? (weatherColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = weatherArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
Ref {
service: WeatherService
}
Column {
id: weatherColumn
visible: root.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.barIconSize(barThickness, -6)
color: Theme.primary
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
if (temp === undefined || temp === null || temp === 0) {
return "--";
}
return temp;
content: Component {
Item {
implicitWidth: {
if (!SettingsData.weatherEnabled) return 0
if (root.isVerticalOrientation) return root.widgetThickness - root.horizontalPadding * 2
return Math.min(100 - root.horizontalPadding * 2, weatherRow.implicitWidth)
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
implicitHeight: root.isVerticalOrientation ? weatherColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Row {
id: weatherRow
Column {
id: weatherColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: 1
visible: !root.isVertical
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.barIconSize(barThickness, -6)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
if (temp === undefined || temp === null || temp === 0) {
return "--°" + (SettingsData.useFahrenheit ? "F" : "C");
DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.barIconSize(root.barThickness, -6)
color: Theme.primary
anchors.horizontalCenter: parent.horizontalCenter
}
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
StyledText {
text: {
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
if (temp === undefined || temp === null || temp === 0) {
return "--";
}
return temp;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
id: weatherRow
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: Theme.spacingXS
MouseArea {
id: weatherArea
DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.barIconSize(root.barThickness, -6)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
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)
StyledText {
text: {
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
if (temp === undefined || temp === null || temp === 0) {
return "--°" + (SettingsData.useFahrenheit ? "F" : "C");
}
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
root.clicked();
}
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -7,7 +7,7 @@ import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
Item {
id: root
property bool isVertical: axis?.isVertical ?? false
@@ -15,6 +15,11 @@ Rectangle {
property string screenName: ""
property real widgetHeight: 30
property real barThickness: 48
property var hyprlandOverviewLoader: null
property var parentScreen: null
readonly property var sortedToplevels: {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
}
property int currentWorkspace: {
if (CompositorService.isNiri) {
return getNiriActiveWorkspace()
@@ -197,9 +202,9 @@ Rectangle {
return currentMonitor.activeWorkspace?.id ?? 1
}
readonly property real padding: isVertical
? Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
: (widgetHeight - workspaceRow.implicitHeight) / 2
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
function getRealWorkspaces() {
return root.workspaceList.filter(ws => {
@@ -219,47 +224,146 @@ Rectangle {
const currentIndex = realWorkspaces.findIndex(ws => ws === root.currentWorkspace)
const validIndex = currentIndex === -1 ? 0 : currentIndex
const nextIndex = direction > 0 ? (validIndex + 1) % realWorkspaces.length : (validIndex - 1 + realWorkspaces.length) % realWorkspaces.length
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0)
if (nextIndex === validIndex) {
return
}
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1)
} else if (CompositorService.isHyprland) {
const command = direction > 0 ? "workspace r+1" : "workspace r-1"
Hyprland.dispatch(command)
const realWorkspaces = getRealWorkspaces()
if (realWorkspaces.length < 2) {
return
}
const currentIndex = realWorkspaces.findIndex(ws => ws.id === root.currentWorkspace)
const validIndex = currentIndex === -1 ? 0 : currentIndex
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0)
if (nextIndex === validIndex) {
return
}
Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`)
}
}
width: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
height: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground)
return "transparent"
const baseColor = Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness
visible: CompositorService.isNiri || CompositorService.isHyprland
Rectangle {
id: visualBackground
width: root.visualWidth
height: root.visualHeight
anchors.centerIn: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground)
return "transparent"
const baseColor = Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
acceptedButtons: Qt.RightButton
property real scrollAccumulator: 0
property real touchpadThreshold: 500
onClicked: mouse => {
if (mouse.button === Qt.RightButton && CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
}
}
onWheel: wheel => {
const deltaY = wheel.angleDelta.y
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
const direction = deltaY < 0 ? 1 : -1
if (isMouseWheel) {
switchWorkspace(direction)
if (!SettingsData.workspaceScrolling || !CompositorService.isNiri) {
switchWorkspace(direction)
}
else {
const windows = root.sortedToplevels;
if (windows.length < 2) {
return;
}
let currentIndex = -1;
for (let i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i;
break;
}
}
let nextIndex;
if (deltaY < 0) {
if (currentIndex === -1) {
nextIndex = 0;
} else {
nextIndex = currentIndex +1;
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length -1;
} else {
nextIndex = currentIndex - 1
}
}
const nextWindow = windows[nextIndex];
if (nextWindow) {
nextWindow.activate();
}
}
} else {
scrollAccumulator += deltaY
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
const touchDirection = scrollAccumulator < 0 ? 1 : -1
switchWorkspace(touchDirection)
scrollAccumulator = 0
if (!SettingsData.workspaceScrolling || !CompositorService.isNiri) {
switchWorkspace(touchDirection)
}
else {
const windows = root.sortedToplevels;
if (windows.length < 2) {
return;
}
let currentIndex = -1;
for (let i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i;
break;
}
}
let nextIndex;
if (deltaY < 0) {
if (currentIndex === -1) {
nextIndex = 0;
} else {
nextIndex = currentIndex +1;
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length -1;
} else {
nextIndex = currentIndex - 1
}
}
const nextWindow = windows[nextIndex];
if (nextWindow) {
nextWindow.activate();
}
}
scrollAccumulator = 0
}
}
@@ -277,7 +381,7 @@ Rectangle {
Repeater {
model: root.workspaceList
Rectangle {
Item {
id: delegateRoot
property bool isActive: {
@@ -309,6 +413,53 @@ Rectangle {
property bool loadedHasIcon: false
property var loadedIcons: []
readonly property real visualWidth: {
if (root.isVertical) {
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5
} else {
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons)
const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0)
const baseWidth = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7
return baseWidth + iconsWidth
}
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7
}
}
readonly property real visualHeight: {
if (root.isVertical) {
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons)
const iconsHeight = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0)
const baseHeight = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7
return baseHeight + iconsHeight
}
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7
} else {
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5
}
}
//DO NOT move this MouseArea. It should be on this level in order for the appMouseArea to work
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: !isPlaceholder
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !isPlaceholder
onClicked: {
if (isPlaceholder) {
return
}
if (CompositorService.isNiri) {
NiriService.switchToWorkspace(modelData - 1)
} else if (CompositorService.isHyprland && modelData?.id) {
Hyprland.dispatch(`workspace ${modelData.id}`)
}
}
}
Timer {
id: dataUpdateTimer
interval: 50
@@ -350,92 +501,54 @@ Rectangle {
dataUpdateTimer.restart()
}
width: {
if (root.isVertical) {
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5;
} else {
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
const baseWidth = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7;
return baseWidth + iconsWidth;
}
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7;
}
}
height: {
if (root.isVertical) {
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
const iconsHeight = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
const baseHeight = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7;
return baseHeight + iconsHeight;
}
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7;
} else {
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5;
}
}
radius: Theme.cornerRadius
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
width: root.isVertical ? root.barThickness : visualWidth
height: root.isVertical ? visualHeight : root.barThickness
border.width: isUrgent && !isActive ? 2 : 0
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
Rectangle {
id: visualContent
width: delegateRoot.visualWidth
height: delegateRoot.visualHeight
anchors.centerIn: parent
radius: Theme.cornerRadius
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
Behavior on width {
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
border.width: isUrgent && !isActive ? 2 : 0
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
Behavior on height {
enabled: root.isVertical && (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on color {
ColorAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: !isPlaceholder
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !isPlaceholder
onClicked: {
if (isPlaceholder) {
return
}
if (CompositorService.isNiri) {
NiriService.switchToWorkspace(modelData - 1)
} else if (CompositorService.isHyprland && modelData?.id) {
Hyprland.dispatch(`workspace ${modelData.id}`)
Behavior on width {
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
Loader {
id: appIconsLoader
anchors.fill: parent
active: SettingsData.showWorkspaceApps
Behavior on height {
enabled: root.isVertical && (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on color {
ColorAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
Loader {
id: appIconsLoader
anchors.fill: parent
active: SettingsData.showWorkspaceApps
sourceComponent: Item {
Loader {
id: contentRow
@@ -475,7 +588,6 @@ Rectangle {
MouseArea {
id: appMouseArea
hoverEnabled: true
anchors.fill: parent
enabled: isActive
cursorShape: Qt.PointingHandCursor
@@ -544,7 +656,6 @@ Rectangle {
MouseArea {
id: appMouseArea
hoverEnabled: true
anchors.fill: parent
enabled: isActive
cursorShape: Qt.PointingHandCursor
@@ -636,8 +747,8 @@ Rectangle {
}
}
}
}
// --- LOGIC / TRIGGERS ---
Component.onCompleted: updateAllData()
Connections {
@@ -649,6 +760,7 @@ Rectangle {
enabled: CompositorService.isNiri
function onAllWorkspacesChanged() { delegateRoot.updateAllData() }
function onWindowUrgentChanged() { delegateRoot.updateAllData() }
function onWindowsChanged() { delegateRoot.updateAllData() }
}
Connections {
target: SettingsData

View File

@@ -16,6 +16,8 @@ DankPopout {
property var triggerScreen: null
property int currentTabIndex: 0
keyboardFocusMode: WlrKeyboardFocus.Exclusive
function setTriggerPosition(x, y, width, section, screen) {
triggerSection = section
triggerScreen = screen
@@ -43,15 +45,49 @@ DankPopout {
shouldBeVisible: dashVisible
visible: shouldBeVisible
property bool __focusArmed: false
property bool __contentReady: false
function __tryFocusOnce() {
if (!__focusArmed) return
const win = root.window
if (!win || !win.visible) return
if (!contentLoader.item) return
if (win.requestActivate) win.requestActivate()
contentLoader.item.forceActiveFocus(Qt.TabFocusReason)
if (contentLoader.item.activeFocus)
__focusArmed = false
}
onDashVisibleChanged: {
if (dashVisible) {
__focusArmed = true
__contentReady = !!contentLoader.item
open()
__tryFocusOnce()
} else {
__focusArmed = false
__contentReady = false
close()
}
}
Connections {
target: contentLoader
function onLoaded() {
__contentReady = true
if (__focusArmed) __tryFocusOnce()
}
}
Connections {
target: root.window ? root.window : null
enabled: !!root.window
function onVisibleChanged() { if (__focusArmed) __tryFocusOnce() }
}
onBackgroundClicked: {
dashVisible = false
}
@@ -67,18 +103,12 @@ DankPopout {
Component.onCompleted: {
if (root.shouldBeVisible) {
forceActiveFocus()
}
}
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
root.dashVisible = false
event.accepted = true
mainContainer.forceActiveFocus()
}
}
Connections {
target: root
function onShouldBeVisibleChanged() {
if (root.shouldBeVisible) {
Qt.callLater(function() {
@@ -86,7 +116,52 @@ DankPopout {
})
}
}
target: root
}
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
root.dashVisible = false
event.accepted = true
return
}
if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) {
let nextIndex = root.currentTabIndex + 1
while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) {
nextIndex++
}
if (nextIndex >= tabBar.model.length) {
nextIndex = 0
}
root.currentTabIndex = nextIndex
event.accepted = true
return
}
if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
let prevIndex = root.currentTabIndex - 1
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
prevIndex--
}
if (prevIndex < 0) {
prevIndex = tabBar.model.length - 1
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
prevIndex--
}
}
if (prevIndex >= 0) {
root.currentTabIndex = prevIndex
}
event.accepted = true
return
}
if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) {
if (wallpaperTab.handleKeyEvent(event)) {
event.accepted = true
return
}
}
}
Rectangle {
@@ -128,17 +203,29 @@ DankPopout {
currentIndex: root.currentTabIndex
spacing: Theme.spacingS
equalWidthTabs: true
enableArrowNavigation: false
focus: false
activeFocusOnTab: false
nextFocusTarget: {
const item = pages.currentItem
if (!item)
return null
if (item.focusTarget)
return item.focusTarget
return item
}
model: {
let tabs = [
{ icon: "dashboard", text: I18n.tr("Overview") },
{ icon: "music_note", text: I18n.tr("Media") }
{ icon: "music_note", text: I18n.tr("Media") },
{ icon: "wallpaper", text: I18n.tr("Wallpapers") }
]
if (SettingsData.weatherEnabled) {
tabs.push({ icon: "wb_sunny", text: I18n.tr("Weather") })
}
tabs.push({ icon: "settings", text: I18n.tr("Settings"), isAction: true })
return tabs
}
@@ -148,7 +235,7 @@ DankPopout {
}
onActionTriggered: function(index) {
let settingsIndex = SettingsData.weatherEnabled ? 3 : 2
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3
if (index === settingsIndex) {
dashVisible = false
settingsModal.show()
@@ -168,7 +255,8 @@ DankPopout {
implicitHeight: {
if (currentIndex === 0) return overviewTab.implicitHeight
if (currentIndex === 1) return mediaTab.implicitHeight
if (SettingsData.weatherEnabled && currentIndex === 2) return weatherTab.implicitHeight
if (currentIndex === 2) return wallpaperTab.implicitHeight
if (SettingsData.weatherEnabled && currentIndex === 3) return weatherTab.implicitHeight
return overviewTab.implicitHeight
}
currentIndex: root.currentTabIndex
@@ -178,8 +266,8 @@ DankPopout {
onSwitchToWeatherTab: {
if (SettingsData.weatherEnabled) {
tabBar.currentIndex = 2
tabBar.tabClicked(2)
tabBar.currentIndex = 3
tabBar.tabClicked(3)
}
}
@@ -193,9 +281,16 @@ DankPopout {
id: mediaTab
}
WallpaperTab {
id: wallpaperTab
active: root.currentTabIndex === 2
tabBarItem: tabBar
keyForwardTarget: mainContainer
}
WeatherTab {
id: weatherTab
visible: SettingsData.weatherEnabled && root.currentTabIndex === 2
visible: SettingsData.weatherEnabled && root.currentTabIndex === 3
}
}
}

View File

@@ -128,9 +128,9 @@ Item {
function getAudioDeviceIcon(device) {
if (!device || !device.name) return "speaker"
const name = device.name.toLowerCase()
if (name.includes("bluez") || name.includes("bluetooth"))
return "headset"
if (name.includes("hdmi"))
@@ -139,10 +139,10 @@ Item {
return "headset"
if (name.includes("analog") || name.includes("built-in"))
return "speaker"
return "speaker"
}
function getVolumeIcon(sink) {
if (!sink || !sink.audio) return "volume_off"
@@ -262,8 +262,8 @@ Item {
maybeFinishSwitch()
}
}
property bool isSeeking: false
@@ -325,7 +325,7 @@ Item {
return mouse.x < item.x || mouse.x > item.x + item.width ||
mouse.y < item.y || mouse.y > item.y + item.height
}
if (playerSelectorButton.playersExpanded && clickOutside(playerSelectorDropdown)) {
playerSelectorButton.playersExpanded = false
}
@@ -400,11 +400,11 @@ Item {
easing.bezierCurve: Anims.standard
}
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
StyledText {
text: I18n.tr("Audio Output Devices (") + audioDevicesDropdown.availableDevices.length + ")"
font.pixelSize: Theme.fontSizeMedium
@@ -414,49 +414,49 @@ Item {
horizontalAlignment: Text.AlignHCenter
bottomPadding: Theme.spacingM
}
DankFlickable {
width: parent.width
height: parent.height - 40
contentHeight: deviceColumn.height
clip: true
Column {
id: deviceColumn
width: parent.width
spacing: Theme.spacingS
Repeater {
model: audioDevicesDropdown.availableDevices
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 48
radius: Theme.cornerRadius
color: deviceMouseAreaLeft.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainerHigh
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: modelData === AudioService.sink ? 2 : 1
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
width: parent.width - Theme.spacingM * 2
DankIcon {
name: getAudioDeviceIcon(modelData)
size: 20
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 20 - Theme.spacingM * 2
StyledText {
text: AudioService.displayName(modelData)
font.pixelSize: Theme.fontSizeMedium
@@ -466,7 +466,7 @@ Item {
width: parent.width
wrapMode: Text.NoWrap
}
StyledText {
text: modelData === AudioService.sink ? "Active" : "Available"
font.pixelSize: Theme.fontSizeSmall
@@ -477,7 +477,7 @@ Item {
}
}
}
MouseArea {
id: deviceMouseAreaLeft
anchors.fill: parent
@@ -490,7 +490,7 @@ Item {
audioDevicesButton.devicesExpanded = false
}
}
Behavior on border.color { ColorAnimation { duration: Anims.durShort } }
}
}
@@ -793,7 +793,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -812,7 +812,7 @@ Item {
Item {
width: parent.width
height: 50
Row {
anchors.centerIn: parent
spacing: Theme.spacingM

View File

@@ -92,12 +92,12 @@ Rectangle {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Item {
width: parent.width
height: 40
visible: showEventDetails
Rectangle {
width: 32
height: 32
@@ -122,7 +122,7 @@ Rectangle {
onClicked: root.showEventDetails = false
}
}
StyledText {
anchors.left: parent.left
anchors.right: parent.right
@@ -149,7 +149,7 @@ Rectangle {
width: parent.width
height: 28
visible: !showEventDetails
Rectangle {
width: 28
height: 28
@@ -215,7 +215,7 @@ Rectangle {
}
}
}
Row {
width: parent.width
height: 18
@@ -248,14 +248,14 @@ Rectangle {
}
}
}
Grid {
id: calendarGrid
visible: !showEventDetails
property date displayDate: systemClock.date
property date selectedDate: systemClock.date
readonly property date firstDay: {
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
return startOfWeek(firstOfMonth)
@@ -341,7 +341,7 @@ Rectangle {
visible: showEventDetails
clip: true
spacing: Theme.spacingXS
delegate: Rectangle {
width: parent ? parent.width : 0
height: eventContent.implicitHeight + Theme.spacingS
@@ -377,7 +377,7 @@ Rectangle {
Column {
id: eventContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -419,7 +419,7 @@ Rectangle {
MouseArea {
id: eventMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor

View File

@@ -4,9 +4,9 @@ import qs.Common
Rectangle {
id: card
property int pad: Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)

View File

@@ -35,7 +35,7 @@ Card {
width: 28
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: {
if (SettingsData.use24HourClock) {
@@ -53,7 +53,7 @@ Card {
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
@@ -66,7 +66,7 @@ Card {
width: 28
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: 48
@@ -102,7 +102,7 @@ Card {
}
}
StyledText {
text: systemClock?.date?.toLocaleDateString(Qt.locale(), "MMM dd")
font.pixelSize: Theme.fontSizeSmall

View File

@@ -92,7 +92,7 @@ Card {
}
// Just using truncated is always true initially idk
property bool shouldUseShort: longTextWidth > availableWidth
text: shouldUseShort ? UserInfoService.shortUptime : UserInfoService.uptime || "up 1h 23m"
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)

View File

@@ -47,18 +47,18 @@ Card {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingL
visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
DankIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: 48
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: {
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
@@ -71,7 +71,7 @@ Card {
color: Theme.surfaceText
font.weight: Font.Light
}
StyledText {
text: WeatherService.getWeatherCondition(WeatherService.weather.wCode)
font.pixelSize: Theme.fontSizeSmall

View File

@@ -0,0 +1,538 @@
import Qt.labs.folderlistmodel
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Modals.FileBrowser
import qs.Services
import qs.Widgets
Item {
id: root
implicitWidth: 700
implicitHeight: 410
property string wallpaperDir: ""
property int currentPage: 0
property int itemsPerPage: 16
property int totalPages: Math.max(1, Math.ceil(wallpaperFolderModel.count / itemsPerPage))
property bool active: false
property Item focusTarget: wallpaperGrid
property Item tabBarItem: null
property int gridIndex: 0
property Item keyForwardTarget: null
property int lastPage: 0
property bool enableAnimation: false
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
property string selectedFileName: ""
signal requestTabChange(int newIndex)
onCurrentPageChanged: {
if (currentPage !== lastPage) {
enableAnimation = false
lastPage = currentPage
}
updateSelectedFileName()
}
onGridIndexChanged: {
updateSelectedFileName()
}
onVisibleChanged: {
if (visible && active) {
setInitialSelection()
}
}
Component.onCompleted: {
loadWallpaperDirectory()
}
onActiveChanged: {
if (active && visible) {
setInitialSelection()
}
}
function handleKeyEvent(event) {
const columns = 4
const currentCol = gridIndex % columns
const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (gridIndex >= 0 && gridIndex < visibleCount) {
const absoluteIndex = currentPage * itemsPerPage + gridIndex
if (absoluteIndex < wallpaperFolderModel.count) {
const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath")
if (filePath) {
SessionData.setWallpaper(filePath.toString().replace(/^file:\/\//, ''))
}
}
}
return true
}
if (event.key === Qt.Key_Right) {
if (gridIndex + 1 < visibleCount) {
gridIndex++
} else if (currentPage < totalPages - 1) {
gridIndex = 0
currentPage++
}
return true
}
if (event.key === Qt.Key_Left) {
if (gridIndex > 0) {
gridIndex--
} else if (currentPage > 0) {
currentPage--
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
gridIndex = prevPageCount - 1
}
return true
}
if (event.key === Qt.Key_Down) {
if (gridIndex + columns < visibleCount) {
gridIndex += columns
} else if (currentPage < totalPages - 1) {
gridIndex = currentCol
currentPage++
}
return true
}
if (event.key === Qt.Key_Up) {
if (gridIndex >= columns) {
gridIndex -= columns
} else if (currentPage > 0) {
currentPage--
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
const prevPageRows = Math.ceil(prevPageCount / columns)
gridIndex = (prevPageRows - 1) * columns + currentCol
gridIndex = Math.min(gridIndex, prevPageCount - 1)
}
return true
}
if (event.key === Qt.Key_PageUp && currentPage > 0) {
gridIndex = 0
currentPage--
return true
}
if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) {
gridIndex = 0
currentPage++
return true
}
if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) {
gridIndex = 0
currentPage = 0
return true
}
if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) {
currentPage = totalPages - 1
const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
gridIndex = Math.max(0, lastPageCount - 1)
return true
}
return false
}
function setInitialSelection() {
if (!SessionData.wallpaperPath || wallpaperFolderModel.count === 0) {
gridIndex = 0
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
return
}
for (let i = 0; i < wallpaperFolderModel.count; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath && filePath.toString().replace(/^file:\/\//, '') === SessionData.wallpaperPath) {
const targetPage = Math.floor(i / itemsPerPage)
const targetIndex = i % itemsPerPage
currentPage = targetPage
gridIndex = targetIndex
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
return
}
}
gridIndex = 0
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
}
function loadWallpaperDirectory() {
const currentWallpaper = SessionData.wallpaperPath
if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) {
if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") {
wallpaperDir = CacheData.wallpaperLastPath
} else {
wallpaperDir = ""
}
return
}
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
}
function updateSelectedFileName() {
if (wallpaperFolderModel.count === 0) {
selectedFileName = ""
return
}
const absoluteIndex = currentPage * itemsPerPage + gridIndex
if (absoluteIndex < wallpaperFolderModel.count) {
const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath")
if (filePath) {
const pathStr = filePath.toString().replace(/^file:\/\//, '')
selectedFileName = pathStr.substring(pathStr.lastIndexOf('/') + 1)
return
}
}
selectedFileName = ""
}
Connections {
target: SessionData
function onWallpaperPathChanged() {
loadWallpaperDirectory()
if (visible && active) {
setInitialSelection()
}
}
}
Connections {
target: wallpaperFolderModel
function onCountChanged() {
if (wallpaperFolderModel.status === FolderListModel.Ready) {
if (visible && active) {
setInitialSelection()
}
updateSelectedFileName()
}
}
function onStatusChanged() {
if (wallpaperFolderModel.status === FolderListModel.Ready && wallpaperFolderModel.count > 0) {
if (visible && active) {
setInitialSelection()
}
updateSelectedFileName()
}
}
}
FolderListModel {
id: wallpaperFolderModel
showDirsFirst: false
showDotAndDotDot: false
showHidden: false
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
showFiles: true
showDirs: false
sortField: FolderListModel.Name
folder: wallpaperDir ? "file://" + wallpaperDir : ""
}
Loader {
id: wallpaperBrowserLoader
active: false
asynchronous: true
sourceComponent: FileBrowserModal {
Component.onCompleted: {
open()
}
browserTitle: "Select Wallpaper Directory"
browserIcon: "folder_open"
browserType: "wallpaper"
showHiddenFiles: false
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
allowStacking: true
onFileSelected: (path) => {
const cleanPath = path.replace(/^file:\/\//, '')
SessionData.setWallpaper(cleanPath)
const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'))
if (dirPath) {
wallpaperDir = dirPath
CacheData.wallpaperLastPath = dirPath
CacheData.saveCache()
}
close()
}
onDialogClosed: {
Qt.callLater(() => wallpaperBrowserLoader.active = false)
}
}
}
Column {
anchors.fill: parent
spacing: 0
Item {
width: parent.width
height: parent.height - 50
GridView {
id: wallpaperGrid
anchors.centerIn: parent
width: parent.width - Theme.spacingS
height: parent.height - Theme.spacingS
cellWidth: width / 4
cellHeight: height / 4
clip: true
enabled: root.active
interactive: root.active
boundsBehavior: Flickable.StopAtBounds
keyNavigationEnabled: false
activeFocusOnTab: false
highlightFollowsCurrentItem: true
highlightMoveDuration: enableAnimation ? Theme.shortDuration : 0
focus: false
highlight: Item {
z: 1000
Rectangle {
anchors.fill: parent
anchors.margins: Theme.spacingXS
color: "transparent"
border.width: 3
border.color: Theme.primary
radius: Theme.cornerRadius
}
}
model: {
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count)
const items = []
for (let i = startIndex; i < endIndex; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath) {
items.push(filePath.toString().replace(/^file:\/\//, ''))
}
}
return items
}
onModelChanged: {
const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0
if (gridIndex !== clampedIndex) {
gridIndex = clampedIndex
}
}
onCountChanged: {
if (count > 0) {
const clampedIndex = Math.min(gridIndex, count - 1)
currentIndex = clampedIndex
positionViewAtIndex(clampedIndex, GridView.Contain)
}
enableAnimation = true
}
Connections {
target: root
function onGridIndexChanged() {
if (wallpaperGrid.count > 0) {
wallpaperGrid.currentIndex = gridIndex
if (!enableAnimation) {
wallpaperGrid.positionViewAtIndex(gridIndex, GridView.Contain)
}
}
}
}
delegate: Item {
width: wallpaperGrid.cellWidth
height: wallpaperGrid.cellHeight
property string wallpaperPath: modelData || ""
property bool isSelected: SessionData.wallpaperPath === modelData
Rectangle {
id: wallpaperCard
anchors.fill: parent
anchors.margins: Theme.spacingXS
color: Theme.surfaceContainerHighest
radius: Theme.cornerRadius
clip: true
Rectangle {
anchors.fill: parent
color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent"
radius: parent.radius
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Image {
id: thumbnailImage
anchors.fill: parent
source: modelData ? `file://${modelData}` : ""
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
smooth: true
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
maskSource: ShaderEffectSource {
sourceItem: Rectangle {
width: thumbnailImage.width
height: thumbnailImage.height
radius: Theme.cornerRadius
}
}
}
}
BusyIndicator {
anchors.centerIn: parent
running: thumbnailImage.status === Image.Loading
visible: running
}
StateLayer {
anchors.fill: parent
cornerRadius: parent.radius
stateColor: Theme.primary
}
MouseArea {
id: wallpaperMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
gridIndex = index
if (modelData) {
SessionData.setWallpaper(modelData)
}
}
}
}
}
}
StyledText {
anchors.centerIn: parent
visible: wallpaperFolderModel.count === 0
text: "No wallpapers found\n\nClick the folder icon below to browse"
font.pixelSize: 14
color: Theme.outline
horizontalAlignment: Text.AlignHCenter
}
}
Column {
width: parent.width
height: 50
Row {
width: parent.width
height: 32
spacing: Theme.spacingS
Item {
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
height: parent.height
}
Row {
id: controlsRow
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
iconSize: 20
buttonSize: 32
enabled: currentPage > 0
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage > 0) {
currentPage--
}
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: wallpaperFolderModel.count > 0 ? `${wallpaperFolderModel.count} wallpapers ${currentPage + 1} / ${totalPages}` : "No wallpapers"
font.pixelSize: 14
color: Theme.surfaceText
opacity: 0.7
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
iconSize: 20
buttonSize: 32
enabled: currentPage < totalPages - 1
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage < totalPages - 1) {
currentPage++
}
}
}
}
DankActionButton {
id: browseButton
anchors.verticalCenter: parent.verticalCenter
iconName: "folder_open"
iconSize: 20
buttonSize: 32
opacity: 0.7
onClicked: wallpaperBrowserLoader.active = true
}
}
StyledText {
width: parent.width
height: 18
text: selectedFileName
font.pixelSize: 12
color: Theme.surfaceText
opacity: 0.5
visible: selectedFileName !== ""
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
}
}
}
}

View File

@@ -132,7 +132,7 @@ Item {
anchors.left: tempText.right
anchors.leftMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
hoverEnabled: true
@@ -540,7 +540,7 @@ Item {
width: (parent.width - Theme.spacingXS * 6) / 7
height: parent.height
radius: Theme.cornerRadius
property var dayDate: {
const date = new Date()
date.setDate(date.getDate() + index)

View File

@@ -252,8 +252,8 @@ Variants {
property real currentScreen: modelData ? modelData : dock.screen
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080
property real maxDockWidth: Math.min(screenWidth * 0.8, 1200)
property real maxDockHeight: Math.min(screenHeight * 0.8, 1200)
property real maxDockWidth: screenWidth * 0.98
property real maxDockHeight: screenHeight * 0.98
height: {
if (dock.isVertical) {

View File

@@ -452,10 +452,10 @@ Item {
anchors.top: SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined
anchors.left: SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
anchors.right: SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -2 : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -2 : 0
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -2 : 0
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -2 : 0
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -(SettingsData.dockSpacing / 2) : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -(SettingsData.dockSpacing / 2) : 0
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -(SettingsData.dockSpacing / 2) : 0
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -(SettingsData.dockSpacing / 2) : 0
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
@@ -486,9 +486,19 @@ Item {
}
Rectangle {
width: appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
height: Math.max(2, actualIconSize * 0.05)
radius: Theme.cornerRadius
width: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
}
height: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return Math.max(2, actualIconSize * 0.05)
}
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
color: {
if (!appData) {
return "transparent"
@@ -533,9 +543,19 @@ Item {
}
Rectangle {
width: Math.max(2, actualIconSize * 0.05)
height: appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
radius: Theme.cornerRadius
width: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return Math.max(2, actualIconSize * 0.05)
}
height: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
}
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
}
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
color: {
if (!appData) {
return "transparent"

View File

@@ -20,6 +20,7 @@ Singleton {
property string customThemeFile: ""
property string matugenScheme: "scheme-tonal-spot"
property bool use24HourClock: true
property bool showSeconds: false
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string weatherLocation: "New York, NY"
@@ -54,6 +55,7 @@ Singleton {
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY"

View File

@@ -7,7 +7,6 @@ import Quickshell
import Quickshell.Io
import Quickshell.Services.Greetd
import Quickshell.Services.Pam
import Quickshell.Services.Mpris
import qs.Common
import qs.Services
import qs.Widgets
@@ -16,8 +15,6 @@ import qs.Modules.Lock
Item {
id: root
required property var sessionLock
readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS")
property string screenName: ""
property string randomFact: ""
@@ -117,21 +114,6 @@ Item {
onTriggered: updateHyprlandLayout()
}
// ! This was for development and testing, just leaving so people can see how I did it.
// Timer {
// id: autoUnlockTimer
// interval: 10000
// running: true
// onTriggered: {
// root.sessionLock.locked = false
// GreeterState.unlocking = true
// const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
// if (sessionCmd) {
// GreetdMemory.setLastSessionId(sessionCmd.split(" ")[0])
// Greetd.launch(sessionCmd.split(" "), [], true)
// }
// }
// }
Connections {
target: GreetdMemory
@@ -173,7 +155,7 @@ Item {
}
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""
}
fillMode: Image.PreserveAspectCrop
fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode)
smooth: true
asynchronous: false
cache: true
@@ -204,7 +186,7 @@ Item {
SystemClock {
id: systemClock
precision: SystemClock.Minutes
precision: SystemClock.Seconds
}
Rectangle {
@@ -214,40 +196,136 @@ Item {
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: -100
width: 400
width: parent.width
height: 140
StyledText {
Row {
id: clockText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: {
const format = GreetdSettings.use24HourClock ? "HH:mm" : "h:mm AP"
spacing: 0
property string fullTimeStr: {
const format = GreetdSettings.use24HourClock
? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm")
: (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP")
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
}
font.pixelSize: 120
font.weight: Font.Light
color: "white"
lineHeight: 0.8
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockText.bottom
anchors.topMargin: -20
text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
property string minutes: timeParts[1] || ""
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
return match ? match[0].trim() : ""
}
property bool hasSeconds: timeParts.length > 2
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: 120
font.weight: Font.Light
color: "white"
}
StyledText {
width: 75
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockText.hasSeconds ? ":" : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 20
text: " "
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
StyledText {
text: clockText.ampm
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
}
StyledText {
anchors.centerIn: parent
anchors.verticalCenterOffset: -10
text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: 80
@@ -673,180 +751,11 @@ Item {
height: 24
color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter
visible: MprisController.activePlayer
}
Row {
spacing: Theme.spacingS
visible: MprisController.activePlayer
anchors.verticalCenter: parent.verticalCenter
Item {
width: 20
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
Loader {
active: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
sourceComponent: Component {
Ref {
service: CavaService
}
}
}
Timer {
running: !CavaService.cavaAvailable && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
interval: 256
repeat: true
onTriggered: {
CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25]
}
}
Row {
anchors.centerIn: parent
spacing: 1.5
Repeater {
model: 6
Rectangle {
width: 2
height: {
if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) {
const rawLevel = CavaService.values[index] || 0
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100
const maxHeight = Theme.iconSize - 2
const minHeight = 3
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight)
}
return 3
}
radius: 1.5
color: "white"
anchors.verticalCenter: parent.verticalCenter
Behavior on height {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standardDecel
}
}
}
}
}
visible: {
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
return keyboardVisible && WeatherService.weather.available
}
StyledText {
text: {
const player = MprisController.activePlayer
if (!player?.trackTitle)
return ""
const title = player.trackTitle
const artist = player.trackArtist || ""
return artist ? title + " • " + artist : title
}
font.pixelSize: Theme.fontSizeLarge
color: "white"
opacity: 0.9
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: Math.min(implicitWidth, 400)
wrapMode: Text.NoWrap
maximumLineCount: 1
}
Row {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: 20
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
visible: MprisController.activePlayer
opacity: (MprisController.activePlayer?.canGoPrevious ?? false) ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: "skip_previous"
size: 12
color: "white"
}
MouseArea {
id: prevArea
anchors.fill: parent
enabled: MprisController.activePlayer?.canGoPrevious ?? false
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.activePlayer?.previous()
}
}
Rectangle {
width: 24
height: 24
radius: 12
anchors.verticalCenter: parent.verticalCenter
color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? Qt.rgba(255, 255, 255, 0.9) : Qt.rgba(255, 255, 255, 0.2)
visible: MprisController.activePlayer
DankIcon {
anchors.centerIn: parent
name: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
size: 14
color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "black" : "white"
}
MouseArea {
anchors.fill: parent
enabled: MprisController.activePlayer
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.activePlayer?.togglePlaying()
}
}
Rectangle {
width: 20
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
visible: MprisController.activePlayer
opacity: (MprisController.activePlayer?.canGoNext ?? false) ? 1 : 0.3
DankIcon {
anchors.centerIn: parent
name: "skip_next"
size: 12
color: "white"
}
MouseArea {
id: nextArea
anchors.fill: parent
enabled: MprisController.activePlayer?.canGoNext ?? false
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.activePlayer?.next()
}
}
}
}
Rectangle {
width: 1
height: 24
color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter
visible: MprisController.activePlayer && WeatherService.weather.available
}
Row {
@@ -1247,7 +1156,6 @@ Item {
}
function onReadyToLaunch() {
root.sessionLock.locked = false
GreeterState.unlocking = true
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
if (sessionCmd) {

View File

@@ -1,18 +1,34 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
WlSessionLockSurface {
id: root
Variants {
model: Quickshell.screens
required property WlSessionLock lock
PanelWindow {
id: root
color: "transparent"
property var modelData
GreeterContent {
anchors.fill: parent
screenName: root.screen?.name ?? ""
sessionLock: root.lock
screen: modelData
anchors {
left: true
right: true
top: true
bottom: true
}
exclusionMode: ExclusionMode.Normal
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
GreeterContent {
anchors.fill: parent
screenName: root.screen?.name ?? ""
}
}
}

View File

@@ -22,36 +22,95 @@ paru -S greetd-dms-greeter-git
yay -S greetd-dms-greeter-git
```
Then in your `/etc/greetd/config.toml` enable dms-greeter by replacing the greeter command with dms-greeter.
```bash
# hyprland and sway are also supported as compositors
command = "/usr/bin/dms-greeter --command niri"
```
See `dms-greeter --help` for full options including custom compositor configurations.
Once installed, you should disable any existing greeter (such as gdm, sddm, lightdm), and you can configure the greeter to run at boot with:
Once installed, disable any existing display manager and enable greetd:
```bash
sudo systemctl disable gdm sddm lightdm
sudo systemctl enable greetd
```
#### Syncing themes
To sync wallpapers, colors, and other settings from the logged in user, you can add your user to the `greeter` group and symlink the shell configurations.
#### Syncing themes (Optional)
To sync your wallpaper and theme with the greeter login screen, follow the manual setup below:
<details>
<summary>Manual theme syncing</summary>
```bash
# Add yourself to greeter group
sudo usermod -aG greeter <username>
# LOGOUT and LOGIN after adding user to group
# Set ACLs to allow greeter to traverse your directories
setfacl -m u:greeter:x ~ ~/.config ~/.local ~/.cache ~/.local/state
ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
# Set group ownership on config directories
sudo chgrp -R greeter ~/.config/DankMaterialShell
sudo chgrp -R greeter ~/.local/state/DankMaterialShell
sudo chgrp -R greeter ~/.cache/quickshell
sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
# Create symlinks
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/colors.json
ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/colors.json
# Logout and login for group membership to take effect
```
</details>
### Fedora / RHEL / Rocky / Alma
Install from COPR or build the RPM:
```bash
# From COPR (when available)
sudo dnf copr enable avenge/dms
sudo dnf install dms-greeter
# Or build locally
cd /path/to/DankMaterialShell
rpkg local
sudo rpm -ivh x86_64/dms-greeter-*.rpm
```
The package automatically:
- Creates the greeter user
- Sets up directories and permissions
- Configures greetd with auto-detected compositor
- Applies SELinux contexts
- Installs the `dms-greeter-sync` helper script
Then disable existing display manager and enable greetd:
```bash
sudo systemctl disable gdm sddm lightdm
sudo systemctl enable greetd
```
#### Syncing themes (Optional)
The RPM package includes the `dms-greeter-sync` helper for easy theme syncing:
```bash
dms-greeter-sync
```
Then logout/login to see your wallpaper on the greeter!
<details>
<summary>What does dms-greeter-sync do?</summary>
The `dms-greeter-sync` helper automatically:
- Adds you to the greeter group
- Sets minimal ACL permissions on parent directories (traverse only)
- Sets group ownership on your DMS config directories
- Creates symlinks to share your theme files with the greeter
This uses standard Linux ACLs (Access Control Lists) - the same security model used by GNOME, KDE, and systemd. The greeter user only gets traverse permission through your directories and can only read the specific theme files you share.
</details>
### Automatic
The easiest thing is to run `dms greeter install` or `dms` for interactive installation.
@@ -59,21 +118,33 @@ The easiest thing is to run `dms greeter install` or `dms` for interactive insta
### Manual
1. Install `greetd` (in most distro's standard repositories) and `quickshell`
2. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`
2. Create the greeter user (if not already created by greetd):
```bash
sudo groupadd -r greeter
sudo useradd -r -g greeter -d /var/lib/greeter -s /bin/bash -c "System Greeter" greeter
sudo mkdir -p /var/lib/greeter
sudo chown greeter:greeter /var/lib/greeter
```
3. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`:
```bash
sudo git clone https://github.com/AvengeMedia/DankMaterialShell.git /etc/xdg/quickshell/dms-greeter
```
3. Copy `assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
4. Copy `Modules/Greetd/assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
```bash
sudo cp assets/dms-greeter /usr/local/bin/dms-greeter
sudo cp /etc/xdg/quickshell/dms-greeter/Modules/Greetd/assets/dms-greeter /usr/local/bin/dms-greeter
sudo chmod +x /usr/local/bin/dms-greeter
```
4. Create greeter cache directory with proper permissions:
5. Create greeter cache directory with proper permissions:
```bash
sudo mkdir -p /var/cache/dms-greeter
sudo chown greeter:greeter /var/cache/dms-greeter
sudo chmod 750 /var/cache/dms-greeter
```
6. Edit or create `/etc/greetd/config.toml`:
```toml
[terminal]
@@ -85,7 +156,13 @@ user = "greeter"
command = "/usr/local/bin/dms-greeter --command niri"
```
Enable the greeter with `sudo systemctl enable greetd`
7. Disable existing display manager and enable greetd:
```bash
sudo systemctl disable gdm sddm lightdm
sudo systemctl enable greetd
```
8. (Optional) Set up theme syncing using the manual ACL method described in the Configuration → Personalization section below
#### Legacy installation (deprecated)
@@ -154,21 +231,31 @@ Simply edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` to change
#### Personalization
Wallpapers and themes and weather and clock formats and things are a TODO on the documentation, but it's configured exactly the same as dms.
The greeter can be personalized with wallpapers, themes, weather, clock formats, and more - configured exactly the same as dms.
You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell.
**Easiest method:** Run `dms-greeter-sync` to automatically sync your DMS theme with the greeter.
The greeter uses the `dms-greeter` group for file access permissions, so ensure your user and the greeter user are both members of this group.
**Manual method:** You can manually synchronize configurations if you want greeter settings to always mirror your shell:
```bash
# For core settings (theme, clock formats, etc)
# Add yourself to the greeter group
sudo usermod -aG greeter $USER
# Set ACLs to allow greeter user to traverse your home directory
setfacl -m u:greeter:x ~ ~/.config ~/.local ~/.cache ~/.local/state
# Set group permissions on DMS directories
sudo chgrp -R greeter ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
# Create symlinks for theme files
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
# For state (mainly you would configure wallpaper in this file)
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
# For wallpaper based theming
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/dms-colors.json
sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/colors.json
# Logout and login for group membership to take effect
```
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
**Advanced:** You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
The cache directory should be owned by `greeter:greeter` with `770` permissions.
The cache directory should be owned by `greeter:greeter` with `770` permissions.

View File

@@ -68,6 +68,33 @@ if [[ -z "$COMPOSITOR" ]]; then
exit 1
fi
locate_dms_config() {
local config_name="$1"
local search_paths=()
local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
search_paths+=("$config_home/quickshell/$config_name")
search_paths+=("/usr/share/quickshell/$config_name")
local config_dirs="${XDG_CONFIG_DIRS:-/etc/xdg}"
IFS=':' read -ra dirs <<< "$config_dirs"
for dir in "${dirs[@]}"; do
if [[ -n "$dir" ]]; then
search_paths+=("$dir/quickshell/$config_name")
fi
done
for path in "${search_paths[@]}"; do
if [[ -f "$path/shell.qml" ]]; then
echo "$path"
return 0
fi
done
return 1
}
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
@@ -81,7 +108,14 @@ QS_CMD="qs"
if [[ "$DMS_PATH" == /* ]]; then
QS_CMD="qs -p $DMS_PATH"
else
QS_CMD="qs -c $DMS_PATH"
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
echo "Located DMS config at: $RESOLVED_PATH" >&2
QS_CMD="qs -p $RESOLVED_PATH"
else
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
exit 1
fi
fi
case "$COMPOSITOR" in

View File

@@ -0,0 +1,284 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Common
import qs.Services
Scope {
id: overviewScope
property bool overviewOpen: false
Loader {
id: hyprlandLoader
active: overviewScope.overviewOpen
asynchronous: false
sourceComponent: Variants {
id: overviewVariants
model: Quickshell.screens
PanelWindow {
id: root
required property var modelData
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
screen: modelData
visible: overviewScope.overviewOpen
color: "transparent"
WlrLayershell.namespace: "quickshell:overview"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
anchors {
top: true
left: true
right: true
bottom: true
}
HyprlandFocusGrab {
id: grab
windows: [root]
active: false
property bool hasBeenActivated: false
onActiveChanged: {
if (active) {
hasBeenActivated = true
}
}
onCleared: () => {
if (hasBeenActivated && overviewScope.overviewOpen) {
overviewScope.overviewOpen = false
}
}
}
Connections {
target: overviewScope
function onOverviewOpenChanged() {
if (overviewScope.overviewOpen) {
grab.hasBeenActivated = false
delayedGrabTimer.start()
} else {
delayedGrabTimer.stop()
grab.active = false
grab.hasBeenActivated = false
}
}
}
Connections {
target: root
function onMonitorIsFocusedChanged() {
if (overviewScope.overviewOpen && root.monitorIsFocused && !grab.active) {
grab.hasBeenActivated = false
grab.active = true
} else if (overviewScope.overviewOpen && !root.monitorIsFocused && grab.active) {
grab.active = false
}
}
}
Timer {
id: delayedGrabTimer
interval: 150
repeat: false
onTriggered: {
if (overviewScope.overviewOpen && root.monitorIsFocused) {
grab.active = true
}
}
}
Timer {
id: closeTimer
interval: Theme.expressiveDurations.expressiveDefaultSpatial + 120
onTriggered: {
root.visible = false
}
}
Rectangle {
id: background
anchors.fill: parent
color: "black"
opacity: overviewScope.overviewOpen ? 0.5 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
}
}
MouseArea {
anchors.fill: parent
onClicked: mouse => {
const localPos = mapToItem(contentContainer, mouse.x, mouse.y)
if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) {
overviewScope.overviewOpen = false
closeTimer.restart()
}
}
}
}
Item {
id: contentContainer
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 100
width: childrenRect.width
height: childrenRect.height
opacity: overviewScope.overviewOpen ? 1 : 0
transform: [scaleTransform, motionTransform]
Scale {
id: scaleTransform
origin.x: contentContainer.width / 2
origin.y: contentContainer.height / 2
xScale: overviewScope.overviewOpen ? 1 : 0.96
yScale: overviewScope.overviewOpen ? 1 : 0.96
Behavior on xScale {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
}
}
Behavior on yScale {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
}
}
}
Translate {
id: motionTransform
x: 0
y: overviewScope.overviewOpen ? 0 : Theme.spacingL
Behavior on y {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
}
}
Loader {
id: overviewLoader
active: overviewScope.overviewOpen
asynchronous: false
sourceComponent: OverviewWidget {
panelWindow: root
overviewOpen: overviewScope.overviewOpen
}
}
}
FocusScope {
id: focusScope
anchors.fill: parent
visible: overviewScope.overviewOpen
focus: overviewScope.overviewOpen && root.monitorIsFocused
Keys.onEscapePressed: event => {
if (!root.monitorIsFocused) return
overviewScope.overviewOpen = false
closeTimer.restart()
event.accepted = true
}
Keys.onPressed: event => {
if (!root.monitorIsFocused) return
if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
if (!overviewLoader.item) return
const thisMonitorWorkspaceIds = overviewLoader.item.thisMonitorWorkspaceIds
if (thisMonitorWorkspaceIds.length === 0) return
const currentId = root.monitor.activeWorkspace?.id ?? thisMonitorWorkspaceIds[0]
const currentIndex = thisMonitorWorkspaceIds.indexOf(currentId)
let targetIndex
if (event.key === Qt.Key_Left) {
targetIndex = currentIndex - 1
if (targetIndex < 0) targetIndex = thisMonitorWorkspaceIds.length - 1
} else {
targetIndex = currentIndex + 1
if (targetIndex >= thisMonitorWorkspaceIds.length) targetIndex = 0
}
const targetId = thisMonitorWorkspaceIds[targetIndex]
Hyprland.dispatch("workspace " + targetId)
event.accepted = true
}
}
onVisibleChanged: {
if (visible && overviewScope.overviewOpen && root.monitorIsFocused) {
Qt.callLater(() => focusScope.forceActiveFocus())
}
}
Connections {
target: root
function onMonitorIsFocusedChanged() {
if (root.monitorIsFocused && overviewScope.overviewOpen) {
Qt.callLater(() => focusScope.forceActiveFocus())
}
}
}
}
onVisibleChanged: {
if (visible && overviewScope.overviewOpen) {
Qt.callLater(() => focusScope.forceActiveFocus())
} else if (!visible) {
grab.active = false
}
}
Connections {
target: overviewScope
function onOverviewOpenChanged() {
if (overviewScope.overviewOpen) {
closeTimer.stop()
root.visible = true
Qt.callLater(() => focusScope.forceActiveFocus())
} else {
closeTimer.restart()
grab.active = false
}
}
}
}
}
}
}

View File

@@ -0,0 +1,428 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
required property var panelWindow
required property bool overviewOpen
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns
readonly property var allWorkspaces: Hyprland.workspaces?.values || []
readonly property var allWorkspaceIds: {
const workspaces = allWorkspaces
if (!workspaces || workspaces.length === 0) return []
try {
const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined)
return ids.sort((a, b) => a - b)
} catch (e) {
return []
}
}
readonly property var thisMonitorWorkspaceIds: {
const workspaces = allWorkspaces
const mon = monitor
if (!workspaces || workspaces.length === 0 || !mon) return []
try {
const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name)
return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b)
} catch (e) {
return []
}
}
readonly property var displayedWorkspaceIds: {
if (!allWorkspaceIds || allWorkspaceIds.length === 0) {
const result = []
for (let i = 1; i <= workspacesShown; i++) {
result.push(i)
}
return result
}
try {
const maxExisting = Math.max(...allWorkspaceIds)
const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length)
const result = []
for (let i = 1; i <= maxExisting; i++) {
result.push(i)
}
let nextId = maxExisting + 1
while (result.length < totalNeeded) {
result.push(nextId)
nextId++
}
return result
} catch (e) {
const result = []
for (let i = 1; i <= workspacesShown; i++) {
result.push(i)
}
return result
}
}
readonly property int minWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[0] : 1
readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
function getWorkspaceMonitorName(workspaceId) {
if (!allWorkspaces || !workspaceId) return ""
try {
const ws = allWorkspaces.find(w => w?.id === workspaceId)
return ws?.monitor?.name ?? ""
} catch (e) {
return ""
}
}
function workspaceHasWindows(workspaceId) {
if (!workspaceId) return false
try {
const workspace = allWorkspaces.find(ws => ws?.id === workspaceId)
if (!workspace) return false
const toplevels = workspace?.toplevels?.values || []
return toplevels.length > 0
} catch (e) {
return false
}
}
property bool monitorIsFocused: monitor?.focused ?? false
property real scale: SettingsData.overviewScale
property color activeBorderColor: Theme.primary
property real workspaceImplicitWidth: ((monitor.width / monitor.scale) * root.scale)
property real workspaceImplicitHeight: ((monitor.height / monitor.scale) * root.scale)
property int workspaceZ: 0
property int windowZ: 1
property int monitorLabelZ: 2
property int windowDraggingZ: 99999
property real workspaceSpacing: 5
property int draggingFromWorkspace: -1
property int draggingTargetWorkspace: -1
implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2
implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2
Component.onCompleted: {
Hyprland.refreshToplevels()
Hyprland.refreshWorkspaces()
Hyprland.refreshMonitors()
}
onOverviewOpenChanged: {
if (overviewOpen) {
Hyprland.refreshToplevels()
Hyprland.refreshWorkspaces()
Hyprland.refreshMonitors()
}
}
Rectangle {
id: overviewBackground
property real padding: 10
anchors.fill: parent
anchors.margins: Theme.spacingL
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowBlur: 0.5
shadowHorizontalOffset: 0
shadowVerticalOffset: 4
shadowColor: Theme.shadowStrong
shadowOpacity: 1
blurMax: 32
}
ColumnLayout {
id: workspaceColumnLayout
z: root.workspaceZ
anchors.centerIn: parent
spacing: workspaceSpacing
Repeater {
model: SettingsData.overviewRows
delegate: RowLayout {
id: row
property int rowIndex: index
spacing: workspaceSpacing
Repeater {
model: SettingsData.overviewColumns
Rectangle {
id: workspace
property int colIndex: index
property int workspaceIndex: rowIndex * SettingsData.overviewColumns + colIndex
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
property bool isActive: workspaceObj?.active ?? false
property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true
property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3)
property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1)
property color hoveredBorderColor: Theme.surfaceVariant
property bool hoveredWhileDragging: false
property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
visible: workspaceValue !== -1
implicitWidth: root.workspaceImplicitWidth
implicitHeight: root.workspaceImplicitHeight
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
radius: Theme.cornerRadius
border.width: 2
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : "transparent")
StyledText {
anchors.centerIn: parent
text: workspaceValue
font.pixelSize: Theme.fontSizeXLarge * 6
font.weight: Font.DemiBold
color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
MouseArea {
id: workspaceArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
if (root.draggingTargetWorkspace === -1) {
root.overviewOpen = false
Hyprland.dispatch(`workspace ${workspaceValue}`)
}
}
}
DropArea {
anchors.fill: parent
onEntered: {
root.draggingTargetWorkspace = workspaceValue
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return
hoveredWhileDragging = true
}
onExited: {
hoveredWhileDragging = false
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
}
}
}
}
}
}
}
Item {
id: windowSpace
anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight
Repeater {
model: ScriptModel {
values: {
const workspaces = root.allWorkspaces
const minId = root.minWorkspaceId
const maxId = root.maxWorkspaceId
if (!workspaces || workspaces.length === 0) return []
try {
const result = []
for (const workspace of workspaces) {
const wsId = workspace?.id ?? -1
if (wsId >= minId && wsId <= maxId) {
const toplevels = workspace?.toplevels?.values || []
for (const toplevel of toplevels) {
result.push(toplevel)
}
}
}
return result
} catch (e) {
console.error("OverviewWidget filter error:", e)
return []
}
}
}
delegate: OverviewWindow {
id: window
required property var modelData
overviewOpen: root.overviewOpen
readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1
function getWorkspaceIndex() {
if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0
if (!windowWorkspaceId || windowWorkspaceId < 0) return 0
try {
for (let i = 0; i < root.displayedWorkspaceIds.length; i++) {
if (root.displayedWorkspaceIds[i] === windowWorkspaceId) {
return i
}
}
return 0
} catch (e) {
return 0
}
}
readonly property int workspaceIndex: getWorkspaceIndex()
readonly property int workspaceColIndex: workspaceIndex % SettingsData.overviewColumns
readonly property int workspaceRowIndex: Math.floor(workspaceIndex / SettingsData.overviewColumns)
toplevel: modelData
scale: root.scale
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
widgetMonitorId: root.monitor.id
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
z: atInitPosition ? root.windowZ : root.windowDraggingZ
property bool atInitPosition: (initX == x && initY == y)
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
MouseArea {
id: dragArea
anchors.fill: parent
hoverEnabled: true
onEntered: window.hovered = true
onExited: window.hovered = false
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
drag.target: parent
onPressed: (mouse) => {
root.draggingFromWorkspace = windowData?.workspace.id
window.pressed = true
window.Drag.active = true
window.Drag.source = window
window.Drag.hotSpot.x = mouse.x
window.Drag.hotSpot.y = mouse.y
}
onReleased: {
const targetWorkspace = root.draggingTargetWorkspace
window.pressed = false
window.Drag.active = false
root.draggingFromWorkspace = -1
root.draggingTargetWorkspace = -1
if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`)
Qt.callLater(() => {
Hyprland.refreshToplevels()
Hyprland.refreshWorkspaces()
Qt.callLater(() => {
window.x = window.initX
window.y = window.initY
})
})
} else {
window.x = window.initX
window.y = window.initY
}
}
onClicked: (event) => {
if (!windowData) return
if (event.button === Qt.LeftButton) {
root.overviewOpen = false
Hyprland.dispatch(`focuswindow address:${windowData.address}`)
event.accepted = true
} else if (event.button === Qt.MiddleButton) {
Hyprland.dispatch(`closewindow address:${windowData.address}`)
event.accepted = true
}
}
}
}
}
}
Item {
id: monitorLabelSpace
anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight
z: root.monitorLabelZ
Repeater {
model: SettingsData.overviewRows
delegate: Item {
id: labelRow
property int rowIndex: index
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
width: parent.width
height: root.workspaceImplicitHeight
Repeater {
model: SettingsData.overviewColumns
delegate: Item {
id: labelItem
property int colIndex: index
property int workspaceIndex: labelRow.rowIndex * SettingsData.overviewColumns + colIndex
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
width: root.workspaceImplicitWidth
height: root.workspaceImplicitHeight
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingS
width: monitorNameText.contentWidth + Theme.spacingS * 2
height: monitorNameText.contentHeight + Theme.spacingXS * 2
radius: Theme.cornerRadius
color: Theme.surface
visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== ""
StyledText {
id: monitorNameText
anchors.centerIn: parent
text: labelItem.workspaceMonitorName
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,140 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Common
Item {
id: root
property var toplevel
property var scale
required property bool overviewOpen
property var availableWorkspaceWidth
property var availableWorkspaceHeight
property bool restrictToWorkspace: true
readonly property var windowData: toplevel?.lastIpcObject || null
readonly property var monitorObj: toplevel?.monitor
readonly property var monitorData: monitorObj?.lastIpcObject || null
property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset
property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset
property real xOffset: 0
property real yOffset: 0
property int widgetMonitorId: 0
property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * scale
property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * scale
property bool hovered: false
property bool pressed: false
property var iconToWindowRatio: 0.25
property var iconToWindowRatioCompact: 0.45
property var entry: DesktopEntries.heuristicLookup(windowData?.class)
property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing")
property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth
x: initX
y: initY
width: Math.min((windowData?.size?.[0] ?? 100) * root.scale, availableWorkspaceWidth)
height: Math.min((windowData?.size?.[1] ?? 100) * root.scale, availableWorkspaceHeight)
opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4
Rectangle {
id: maskRect
width: root.width
height: root.height
radius: Theme.cornerRadius
visible: false
layer.enabled: true
}
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: maskRect
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
Behavior on x {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
Behavior on y {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
Behavior on width {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
Behavior on height {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
ScreencopyView {
id: windowPreview
anchors.fill: parent
captureSource: root.overviewOpen ? root.toplevel?.wayland : null
live: true
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) :
hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) :
Theme.withAlpha(Theme.surfaceContainer, 0.1)
border.color: Theme.withAlpha(Theme.outline, 0.3)
border.width: 1
}
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Theme.fontSizeSmall * 0.5
Image {
id: windowIcon
property var iconSize: {
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1)
}
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
width: iconSize
height: iconSize
sourceSize: Qt.size(iconSize, iconSize)
Behavior on width {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
Behavior on height {
NumberAnimation {
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
}
}
}
}
}
}

View File

@@ -18,7 +18,7 @@ Item {
keyboard.target = keyboard_controller.target;
isKeyboardActive = true;
} else
console.info("The keyboard is already shown");
console.log("The keyboard is already shown");
}
function hide() {
@@ -26,7 +26,7 @@ Item {
keyboard.destroy();
isKeyboardActive = false;
} else
console.info("The keyboard is already hidden");
console.log("The keyboard is already hidden");
}
// private

View File

@@ -19,6 +19,10 @@ Scope {
}
function lock() {
if (SettingsData.customPowerActionLock && SettingsData.customPowerActionLock.length > 0) {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLock])
return
}
if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
DMSService.lockSession(response => {
if (response.error) {
@@ -78,12 +82,16 @@ Scope {
locked: shouldLock
WlSessionLockSurface {
id: lockSurface
color: "transparent"
LockSurface {
anchors.fill: parent
lock: sessionLock
sharedPasswordBuffer: root.sharedPasswordBuffer
screenName: lockSurface.screen?.name ?? ""
isLocked: shouldLock
onUnlockRequested: {
root.unlock()
}
@@ -102,7 +110,16 @@ Scope {
target: "lock"
function lock() {
root.lock()
if (!root.processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
DMSService.lockSession(response => {
if (response.error) {
console.warn("Lock: Failed to call loginctl.lock:", response.error)
root.shouldLock = true
}
})
} else {
root.shouldLock = true
}
}
function demo() {

View File

@@ -4,6 +4,7 @@ import QtCore
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Window
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
@@ -23,6 +24,8 @@ Item {
property string hyprlandCurrentLayout: ""
property string hyprlandKeyboard: ""
property int hyprlandLayoutCount: 0
property bool lockerReadySent: false
property bool lockerReadyArmed: false
signal unlockRequested
@@ -43,15 +46,7 @@ Item {
hyprlandLayoutUpdateTimer.start()
}
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
DMSService.sendRequest("loginctl.lockerReady", null, response => {
if (response.error) {
console.warn("LockScreenContent: Failed to signal locker ready:", response.error)
} else {
console.log("LockScreenContent: Locker ready signaled, inhibitor released")
}
})
}
lockerReadyArmed = true
}
onDemoModeChanged: {
if (demoMode) {
@@ -65,6 +60,37 @@ Item {
}
}
function sendLockerReadyOnce() {
if (lockerReadySent) return;
lockerReadySent = true;
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
DMSService.sendRequest("loginctl.lockerReady", null, resp => {
if (resp?.error) console.warn("lockerReady failed:", resp.error)
else console.log("lockerReady sent (afterAnimating/afterRendering)");
});
}
}
function maybeSend() {
if (!lockerReadyArmed) return;
if (!root.visible || root.opacity <= 0) return;
Qt.callLater(() => {
if (root.visible && root.opacity > 0)
sendLockerReadyOnce();
});
}
Connections {
target: root.Window.window
enabled: target !== null
function onAfterAnimating() { maybeSend(); }
function onAfterRendering() { maybeSend(); }
}
onVisibleChanged: maybeSend()
onOpacityChanged: maybeSend()
function updateHyprlandLayout() {
if (CompositorService.isHyprland) {
hyprlandLayoutProcess.running = true
@@ -139,7 +165,7 @@ Item {
}
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""
}
fillMode: Image.PreserveAspectCrop
fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode)
smooth: true
asynchronous: false
cache: true
@@ -171,7 +197,7 @@ Item {
SystemClock {
id: systemClock
precision: SystemClock.Minutes
precision: SystemClock.Seconds
}
Rectangle {
@@ -181,39 +207,134 @@ Item {
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: -100
width: 400
width: parent.width
height: 140
StyledText {
Row {
id: clockText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: {
return systemClock.date.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: 120
font.weight: Font.Light
color: "white"
lineHeight: 0.8
}
spacing: 0
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockText.bottom
anchors.topMargin: -20
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
property string fullTimeStr: {
const format = SettingsData.getEffectiveTimeFormat()
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
}
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
property string minutes: timeParts[1] || ""
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
return match ? match[0].trim() : ""
}
property bool hasSeconds: timeParts.length > 2
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: 120
font.weight: Font.Light
color: "white"
}
StyledText {
width: 75
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockText.hasSeconds ? ":" : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 20
text: " "
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
StyledText {
text: clockText.ampm
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
}
StyledText {
anchors.centerIn: parent
anchors.verticalCenterOffset: -25
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
ColumnLayout {
anchors.centerIn: parent
anchors.verticalCenterOffset: 50
@@ -324,9 +445,6 @@ Item {
onAccepted: {
if (!demoMode && !pam.passwd.active) {
console.log("Enter pressed, starting PAM authentication")
if (pam.fprint.active) {
pam.fprint.abort()
}
pam.passwd.start()
}
}
@@ -574,9 +692,6 @@ Item {
onClicked: {
if (!demoMode) {
console.log("Enter button clicked, starting PAM authentication")
if (pam.fprint.active) {
pam.fprint.abort()
}
pam.passwd.start()
}
}

View File

@@ -10,6 +10,8 @@ Rectangle {
required property WlSessionLock lock
required property string sharedPasswordBuffer
required property string screenName
required property bool isLocked
signal passwordChanged(string newPassword)
signal unlockRequested()
@@ -17,10 +19,12 @@ Rectangle {
color: "transparent"
LockScreenContent {
id: lockContent
anchors.fill: parent
demoMode: false
passwordBuffer: root.sharedPasswordBuffer
screenName: ""
screenName: root.screenName
onUnlockRequested: root.unlockRequested()
onPasswordBufferChanged: {
if (root.sharedPasswordBuffer !== passwordBuffer) {
@@ -28,4 +32,10 @@ Rectangle {
}
}
}
onIsLockedChanged: {
if (!isLocked) {
lockContent.unlocking = false
}
}
}

View File

@@ -28,6 +28,10 @@ Item {
signal hideRequested()
Ref {
service: NotepadStorageService
}
function hasUnsavedChanges() {
return textEditor.hasUnsavedChanges()
}

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