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

Compare commits

...

389 Commits

Author SHA1 Message Date
github-actions[bot]
3963c98689 Update VERSION to v0.4.2 (from DMS) 2025-11-08 14:29:53 +00:00
bbedward
02c59636fc i18n: add polish 2025-11-08 09:12:58 -05:00
bbedward
989f196894 plugins: fix persistence of some settings 2025-11-08 08:27:57 -05:00
bbedward
9314de4772 compossitor: fix scale check 2025-11-08 08:06:42 -05:00
bbedward
44a6cd88cd Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-11-08 01:04:57 -05:00
bbedward
d8774c4787 dms: fix missing subs 2025-11-08 01:04:43 -05:00
nebu
a56066bac1 Hyprkeybind: minor fixes (#660)
* HyperKeybindsModal: size to screen

* HyperKeybindsModal: close on Esc
2025-11-08 00:42:09 -05:00
bbedward
8a96f71d10 matugen: remove surface shifting option entirely 2025-11-07 23:52:33 -05:00
bbedward
20a684e8f5 settings: fix time & weather settings
- broke after recent refactor
2025-11-07 23:29:02 -05:00
bbedward
c8fcf50095 ignore compositor scales when QT DPI is overwritten 2025-11-07 20:07:48 -05:00
bbedward
58b637bcca dock: add margin option
fixes #658
2025-11-07 16:37:41 -05:00
Bruno Cesar Rocha
86caf92c90 fix: Matugen relative paths (#656)
* fix: Matugen relative paths

The Problem

All matugen config files (matugen/configs/*.toml) used relative paths like:

input_path = './matugen/templates/gtk-colors.css'

However, matugen was interpreting the relative paths ./matugen/templates/ as relative to its current execution context (which could be /tmp or another directory), not relative to $SHELL_DIR. This caused the "template doesn't exist" warnings and the "Failed to get input and output paths from hashmap" errors.

The Fix

Modified scripts/matugen-worker.sh to replace all relative template paths with absolute paths before passing them to matugen:

`sed "s|input_path = '\./matugen/templates/|input_path = '$SHELL_DIR/matugen/templates/|g"`

* matugen: leave user-templates as-is

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2025-11-07 13:58:19 -05:00
bbedward
35ead280d5 rendering: improve rendering of popouts and modals 2025-11-07 12:35:15 -05:00
bbedward
5d6c3e364d matugen: fix vibrant scheme 2025-11-07 09:50:22 -05:00
Massimo Branchini
f006175829 Small improvement: handled expansion content in case of missing print server or printers (#655) 2025-11-07 08:46:27 -05:00
Rishi Vora
3e0f325734 update flake.lock (#652) 2025-11-07 08:43:01 -05:00
bbedward
f8d383cff0 silent pre-commit hook 2025-11-06 23:19:10 -05:00
purian23
f2ec3ae755 fix: Update fully charged battery logic 2025-11-06 21:23:36 -05:00
bbedward
f95e4e016b fedora: restart on USR1 instead of HUP 2025-11-06 20:27:32 -05:00
bbedward
898e9e67d0 logo: use nerd fonts for some distros 2025-11-06 18:48:09 -05:00
bbedward
15983921b0 dankbar: allow overriding goth radius
fixes #648
2025-11-06 17:20:18 -05:00
bbedward
65c2077e30 meta: add disable hot reload option 2025-11-06 16:03:11 -05:00
Aleksandr Lebedev
946a28d3be Fix: missing system logo and app icons on Guix System (#616)
* Fix for Guix logo not being shown

* Fixed icons not being shown in Workspace Switcher. Also added a DesktopService with a function to get the icon path

* Fixed some icons not being shown + Icons in app drawer

* Fixed icons not appearing in Spotlight

* Adapted missing icons in app launcher/spotlight

* Removed (now) useless change
2025-11-06 12:51:22 -05:00
bbedward
69accb5319 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-11-06 12:37:42 -05:00
bbedward
4ddcf4391a i18n: move translation checking to pre-commit hook 2025-11-06 12:37:26 -05:00
Aleksandr Lebedev
a0d886009a Someone forgot to rename function calls (#645) 2025-11-06 12:13:09 -05:00
github-actions[bot]
91c37aaa96 i18n: update translations 2025-11-06 15:26:57 +00:00
Moraxyc Xu
7602247558 nix: restart service on dms update (#636) 2025-11-06 10:26:27 -05:00
github-actions[bot]
d5a4035bef Update VERSION to v0.4.1 (from DMS) 2025-11-06 15:19:12 +00:00
bbedward
d9652c7334 brightness: allow overriding exponent 2025-11-06 09:30:47 -05:00
github-actions[bot]
9b4fd7449b i18n: update translations 2025-11-06 13:11:46 +00:00
Massimo Branchini
bc6b568f7e cups plugin: small fix - change state update (#637)
* change state update

* i18n: update source strings from codebase

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-11-06 08:11:18 -05:00
purian23
c9ee856f91 Remove Systemd pre.targets 2025-11-05 23:36:14 -05:00
github-actions[bot]
2355c898f8 Update VERSION to v0.4.0 (from DMS) 2025-11-06 03:13:08 +00:00
bbedward
87d0aed4ad settings: fix plugin settings not being created 2025-11-05 21:09:14 -05:00
github-actions[bot]
3cbc547e0f i18n: update source strings from codebase 2025-11-05 23:11:26 +00:00
bbedward
e883ebe307 cups: only subscribe to cups, if widget is present 2025-11-05 18:10:54 -05:00
purian23
20d797320a Link greeter docs 2025-11-05 18:10:21 -05:00
github-actions[bot]
bc3e043192 i18n: update translations 2025-11-05 21:16:05 +00:00
github-actions[bot]
c0555aa608 i18n: update source strings from codebase 2025-11-05 21:15:53 +00:00
bbedward
e75b47c21a cups: sync with API changes 2025-11-05 16:15:11 -05:00
Massimo Branchini
3702f493f6 CUPS cc integrated widget and service (#632)
* CUPS cc integrated widget and service

* i18n: update source strings from codebase
2025-11-05 16:04:54 -05:00
github-actions[bot]
80d88d4d8f i18n: update source strings from codebase 2025-11-05 18:57:30 +00:00
claymorwan
95c711ce7e feat: layer namespaces (#635) 2025-11-05 13:56:56 -05:00
bbedward
0ee89920fd cc: fix ccWidgetExpanded signal
fixes #633
2025-11-05 12:26:49 -05:00
bbedward
fd49a171c0 errors: generally, handle errors more gracefully with toasts 2025-11-05 12:22:02 -05:00
bbedward
16ad5221eb matugen: update for new dank16 usage 2025-11-05 10:47:40 -05:00
bbedward
8167c432b8 brightness: rename logarithmic to exponential 2025-11-05 10:18:19 -05:00
bbedward
ae5d6c1ba4 brightness: optimistically update OSDs, works better 2025-11-05 10:00:42 -05:00
bbedward
1e6c80bd03 workspace: tweak animations slightly 2025-11-05 09:32:50 -05:00
bbedward
d48cd1acdd brightness: remember max val 2025-11-05 09:28:00 -05:00
github-actions[bot]
a5f92165fb i18n: update translations 2025-11-05 14:24:02 +00:00
bbedward
d834124a71 ddc use raw values not percent 2025-11-05 09:23:14 -05:00
purian23
ce6f3afb39 Remove conflicting dms-cli 2025-11-05 01:16:07 -05:00
purian23
f5462fa1bf Prep global dms greeter sync 2025-11-05 00:29:38 -05:00
bbedward
f75e23158a cc: fix settings usage 2025-11-04 23:25:33 -05:00
github-actions[bot]
253ff71a0a i18n: update source strings from codebase 2025-11-05 04:00:31 +00:00
bbedward
8d7db49cb0 dankbar: add swap option to dankbar
fixes #556
2025-11-04 22:59:47 -05:00
bbedward
315509f7a4 clock: fix settings key mismap 2025-11-04 22:47:24 -05:00
bbedward
a7bd8b810b matugen: add vscodium theme 2025-11-04 22:33:16 -05:00
bbedward
a7c8ba332b spotlight: fix potential binding loop 2025-11-04 20:38:42 -05:00
bbedward
40cadb6a00 spotlight: shrink slightly 2025-11-04 20:31:01 -05:00
purian23
cbf409dffc Remove rate limiting 2025-11-04 17:52:25 -05:00
bbedward
60a791442e niri: allow using satty as the editor 2025-11-04 17:43:23 -05:00
purian23
528e8bf92e Ensure success, optimize stable spec 2025-11-04 17:27:31 -05:00
github-actions[bot]
8a4243e7f8 i18n: update translations 2025-11-04 22:09:14 +00:00
bbedward
1ac95f0d14 displays: allow choosing logarithmic mode for backlight devices 2025-11-04 17:01:50 -05:00
github-actions[bot]
cd51eb25ce i18n: update translations 2025-11-04 21:30:35 +00:00
purian23
4e64a2b2b2 Silence 2025-11-04 16:30:07 -05:00
github-actions[bot]
672c660c41 i18n: update translations 2025-11-04 21:22:34 +00:00
purian23
2a56e57490 Simplify Copr spec 2025-11-04 16:22:04 -05:00
bbedward
f6efd2363a dankbar: allow lower padding levels 2025-11-04 14:09:12 -05:00
bbedward
fa08b39bb0 niri: add screenshot IPCs with swappy 2025-11-04 13:39:51 -05:00
César Sagaert
81c3110d0d disable auto padding for blurred wallpaper (#628)
fixes the vignette around the blurred image
2025-11-04 13:29:07 -05:00
github-actions[bot]
c01e636421 i18n: update translations 2025-11-04 18:06:30 +00:00
github-actions[bot]
fd8d2961bf i18n: update source strings from codebase 2025-11-04 18:06:21 +00:00
bbedward
9e4b53e20b power: replace hibernate with "suspend behavior" opt 2025-11-04 13:05:48 -05:00
bbedward
20116b3933 settings: refactor for maintainability 2025-11-04 12:58:50 -05:00
purian23
bca5ee0c0d Enable SIGHUP non-systemd restart 2025-11-04 11:12:46 -05:00
BB
331bd69021 enable gh sponsors 2025-11-04 10:47:42 -05:00
bbedward
57b11b7699 plugins: keyboard focus to plugin popouts 2025-11-04 10:38:04 -05:00
bbedward
3e9b11c281 popout: keyboard focus fix 2025-11-04 10:29:16 -05:00
github-actions[bot]
bbfd618626 i18n: update translations 2025-11-04 15:23:48 +00:00
bbedward
00abb839f9 dock: add preventStealing 2025-11-04 10:22:50 -05:00
bbedward
1d639d5f5a weather: fix rain chance 2025-11-04 08:42:19 -05:00
github-actions[bot]
c565fc08c3 i18n: update translations 2025-11-04 13:41:42 +00:00
github-actions[bot]
026c71f9fc i18n: update source strings from codebase 2025-11-04 13:41:32 +00:00
bbedward
1eed499151 meta: consistent transparency for all popups/modals 2025-11-04 08:40:26 -05:00
purian23
21f2aabd58 Merge pull request #623 from avktech78/charge-fix
Fix display of the status of multiple batteries
2025-11-04 00:24:59 -05:00
Oleksandr
e1f06b7139 Fix display of the status of multiple batteries
When there are several batteries, one of them is fully charged, and the other is discharging, this leads to the incorrect display of the overall status as “Charging”
2025-11-04 07:22:26 +02:00
purian23
c4be74bce5 Update copr spec to detect systemd upon upgrade 2025-11-04 00:13:50 -05:00
purian23
7c9e9e1cd9 Update systemd dms service 2025-11-03 23:36:37 -05:00
bbedward
797aabc637 matugen: pass -ghostty to dank16 explicitly 2025-11-03 22:33:49 -05:00
bbedward
630a3d4845 matugen: fix vscode light 2025-11-03 21:56:10 -05:00
github-actions[bot]
3b0bb4ea74 i18n: update translations 2025-11-04 02:29:35 +00:00
github-actions[bot]
e36347a4c3 i18n: update source strings from codebase 2025-11-04 02:29:26 +00:00
bbedward
4f59dfc49c namespace tweaks for blur and layer targets 2025-11-03 21:28:40 -05:00
github-actions[bot]
fc0082a470 i18n: update translations 2025-11-04 02:26:22 +00:00
bbedward
712449674f fix hypr workspace right click 2025-11-03 21:25:44 -05:00
github-actions[bot]
a64b4527f2 Update VERSION to v0.3.4 (from DMS) 2025-11-03 23:35:17 +00:00
github-actions[bot]
71a7ebbfe2 Update VERSION to v0.3.3 (from DMS) 2025-11-03 21:43:00 +00:00
ffoebel
d6d701c722 matugen: missing foot.ini colors section (#620) 2025-11-03 16:40:54 -05:00
bbedward
43fbbc07f5 brightness: fix osd suppression 2025-11-03 16:27:41 -05:00
purian23
8504144c32 Jk - no udev rules here 2025-11-03 16:19:11 -05:00
ffoebel
1d3e59b5dd matugen: pywalfox update via post_hook (#619) 2025-11-03 16:00:26 -05:00
bbedward
3f70ca3506 brightness: dont cap to 1 minimum for non-backlight/ddc 2025-11-03 15:39:49 -05:00
purian23
3640d8bd24 Update Copr udev spec source 2025-11-03 15:34:56 -05:00
bbedward
706a99817f wallpaper: small ui tweak 2025-11-03 15:30:55 -05:00
purian23
4645b2dcab Remove brightnessctl dep from Copr specs 2025-11-03 15:22:12 -05:00
bbedward
13f1673371 toast: handle error overflow better 2025-11-03 15:18:06 -05:00
github-actions[bot]
7d374c4c2a i18n: update source strings from codebase 2025-11-03 20:04:07 +00:00
bbedward
5ed449773c lock: prevent sending lockerReady during unlock 2025-11-03 15:03:31 -05:00
github-actions[bot]
aef9c2269a i18n: update translations 2025-11-03 19:57:43 +00:00
github-actions[bot]
daa5a3e821 i18n: update source strings from codebase 2025-11-03 19:57:32 +00:00
bbedward
5cd1167b28 net: add auto connect option for wifi networks
fixes #597
2025-11-03 14:56:49 -05:00
github-actions[bot]
21e7ae3dfd i18n: update translations 2025-11-03 19:17:48 +00:00
bbedward
5d40138585 display: fix brightness OSD suppression 2025-11-03 14:17:10 -05:00
github-actions[bot]
893fd820a3 i18n: update translations 2025-11-03 18:27:17 +00:00
github-actions[bot]
2d536d99e5 i18n: update source strings from codebase 2025-11-03 18:27:06 +00:00
bbedward
a0ee4792b9 greeter: fix cornerRadius and fillmode sync
fixes #609
2025-11-03 13:26:21 -05:00
bbedward
a8f6880840 wallpaper: improve per-mode wallpaper selection interface
- Separate it out so its clear what you're changing
fixes #611
2025-11-03 12:11:33 -05:00
bbedward
51296d1d44 niri: skip wallpaper transition during mode switches
fixes #612
2025-11-03 12:01:00 -05:00
bbedward
0f29149014 dankbar: fix mousearea position for scrolling workspaces
fixes #610
2025-11-03 11:55:41 -05:00
github-actions[bot]
b9f0c277ec i18n: update source strings from codebase 2025-11-03 15:59:32 +00:00
github-actions[bot]
69964c9704 i18n: update translations 2025-11-03 15:59:12 +00:00
github-actions[bot]
ff1d38e34f i18n: update source strings from codebase 2025-11-03 15:59:02 +00:00
Bruno Cesar Rocha
3abee7f2f5 Consolidate launcher (#615)
* refactor: Consolidate Icon Renderer for launcher

Launcher icons are built on 2 places Spotlight and AppDrawer
This duplicates the maintanance effort, every time something
changes on one place must be replicated on the other.

This commit consolidates the Icon renderer in a shared component.

* refactor: Consolidate Launcher list and grid

List and GRid builders were split in 2 components.

this commit adds separate delegates to be reused as shared components.
2025-11-03 10:58:52 -05:00
bbedward
ed0b80008f brightness: use brightness.decrement/increment/refresh APIs 2025-11-03 10:57:16 -05:00
bbedward
976ff108b3 brightness: remove brightnessctl + ddcutil dependencies
- Switches to DMS API v13+ dependency
2025-11-02 20:22:45 -05:00
purian23
66e3cc77c5 Update handling of Systemd 2025-11-02 19:40:47 -05:00
github-actions[bot]
229abba1e4 i18n: update translations 2025-11-03 00:22:59 +00:00
bbedward
8dacaf84cc matugen: color panel border primary 2025-11-02 19:22:20 -05:00
purian23
102b185572 Check initial plugin status 2025-11-02 14:26:24 -05:00
github-actions[bot]
52ac474f7d i18n: update source strings from codebase 2025-11-02 19:21:00 +00:00
purian23
c0064cfcfa Update Notepad initial services 2025-11-02 14:20:33 -05:00
Aziz Hasanain
414ce5610d Remove wallpaper engine support in favor of plugin (#601)
* Remove wallpaper engine support in favor of plugin

* i18n: update source strings from codebase

* Add migration notification for WallpaperEngine support

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-11-02 13:44:06 -05:00
github-actions[bot]
113ac42814 i18n: update translations 2025-11-02 16:08:29 +00:00
github-actions[bot]
a2f2eef326 i18n: update source strings from codebase 2025-11-02 16:08:20 +00:00
bbedward
54a69a6101 gamma: allow setting high temp 2025-11-02 11:07:47 -05:00
bbedward
5e2d3c8d7d update readme 2025-11-02 10:12:31 -05:00
bbedward
5a9950a7c3 matugen: add foot and alacritty 2025-11-02 10:07:20 -05:00
OpetBrebet
2aadbc1a61 Fix dark spot in disc shader after transition (#604) 2025-11-02 09:35:04 -05:00
bbedward
749414ab65 matugen: vscode update 2025-11-02 08:43:48 -05:00
bbedward
baaebcd413 matugen: add vscode theme, switch to dms dank16 2025-11-02 01:42:48 -04:00
purian23
5a8a60b15d Integrate danksearch in DMS Copr 2025-11-01 23:53:33 -04:00
github-actions[bot]
f0ddb8db49 i18n: update translations 2025-11-02 02:39:36 +00:00
bbedward
baa12c0161 matugen: alt version detection 2025-11-01 22:38:56 -04:00
bbedward
ca226e98c2 add contributing docs section 2025-11-01 13:53:07 -04:00
bbedward
453079ef1f update stock wallpaper 2025-11-01 13:28:10 -04:00
github-actions[bot]
074aea2c35 Update VERSION to v0.3.2 (from DMS) 2025-11-01 17:12:54 +00:00
bbedward
9cf5f0b9b3 readme update 2025-11-01 12:30:35 -04:00
bbedward
89e12eea29 readme updoot 2025-11-01 12:26:11 -04:00
bbedward
03d4caff8f matugen: validation 2025-11-01 12:04:59 -04:00
bbedward
89d54dedb7 matugen: more flexible checking 2025-11-01 11:49:12 -04:00
bbedward
9a9e62ccd3 matugen: support newer json format 2025-11-01 11:40:18 -04:00
Massimo Branchini
eca38ae920 base activation for cups capability (#591)
* base activation for cups capability

* i18n: update source strings from codebase

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-11-01 10:55:27 -04:00
bbedward
84e89599bf widgets: fix right click handling 2025-11-01 10:39:25 -04:00
bbedward
e1cdf4ed50 keyboard/hyprland: sync keyboard layout with event 2025-11-01 10:35:22 -04:00
github-actions[bot]
e4371ea4fc i18n: update source strings from codebase 2025-11-01 14:23:09 +00:00
bbedward
9c45d13cbf lock/greeter: don't elide password field
fixes #593
2025-11-01 10:22:27 -04:00
bbedward
5f22347d7a audio: re-create players on default device change 2025-11-01 10:14:30 -04:00
Moraxyc Xu
ca786a3567 nix: replace pkgs.system with pkgs.stdenv.hostPlatform.system (#599) 2025-11-01 09:39:03 -04:00
purian23
60ce662d49 Update Copr path directories 2025-10-31 21:31:45 -04:00
github-actions[bot]
4f9b0d8925 i18n: update translations 2025-11-01 01:09:17 +00:00
purian23
9c2fc570e6 feat: Add Fedora Copr Systemd Support
- Updated distro filestructure
2025-10-31 21:08:47 -04:00
github-actions[bot]
0ba982b271 i18n: update source strings from codebase 2025-10-31 21:10:22 +00:00
Bruno Cesar Rocha
ff3123e387 feat: allow Launcher plugins to set unicode icons. (#594)
Launcher plugins can now set `icon: "unicode:🍉"`
and the symbol is used as the icon.
2025-10-31 17:10:00 -04:00
github-actions[bot]
1548286083 Update VERSION to v0.3.1 (from DMS) 2025-10-31 20:56:24 +00:00
github-actions[bot]
c018d953b8 i18n: update source strings from codebase 2025-10-31 20:32:22 +00:00
bbedward
cf66d28774 about tab: replace ansi art with logo 2025-10-31 16:31:35 -04:00
bbedward
9cec6fd212 update readme 2025-10-31 13:37:42 -04:00
bbedward
92926331b5 layers: up texture quality 2025-10-31 12:06:02 -04:00
github-actions[bot]
f9932ea222 i18n: update translations 2025-10-31 15:51:55 +00:00
github-actions[bot]
a65d6b7630 i18n: update source strings from codebase 2025-10-31 15:51:46 +00:00
bbedward
7252d1e4d7 polkit: simplify service usage 2025-10-31 11:51:11 -04:00
bbedward
3b5a951431 confirm modal: spacing adjustment 2025-10-31 10:08:18 -04:00
bbedward
0b1c331705 power: resize confirmation modals 2025-10-31 09:58:20 -04:00
github-actions[bot]
3c354b71f5 i18n: update translations 2025-10-31 13:40:55 +00:00
github-actions[bot]
1eb5f381ae i18n: update source strings from codebase 2025-10-31 13:40:44 +00:00
bbedward
c5efd28781 polkit: support for polkit escalation prompts 2025-10-31 09:40:05 -04:00
github-actions[bot]
53ae8ac917 i18n: update translations 2025-10-31 04:00:52 +00:00
bbedward
505b6368e6 settings: wrap sidebar in flickable
fixes #581
2025-10-31 00:00:01 -04:00
bbedward
3c20e9e203 dankdash: show mangowc/sway when on one 2025-10-30 22:07:19 -04:00
github-actions[bot]
43427461f5 i18n: update translations 2025-10-31 01:59:35 +00:00
github-actions[bot]
d7740ff6d2 i18n: update source strings from codebase 2025-10-31 01:59:24 +00:00
bbedward
1fb4eb33aa dwl: don't always show tag 1 2025-10-30 21:58:26 -04:00
github-actions[bot]
b27f362b44 Update VERSION to v0.3.0 (from DMS) 2025-10-30 18:11:33 +00:00
bbedward
325e3bc19b fix duplicated qt6ct sections 2025-10-30 13:53:47 -04:00
bbedward
9215985335 ci: try and fix changelog filter 2025-10-30 13:45:39 -04:00
Mattias
293179daa6 fix: Enable "Show on Last Display" for Notepad Slideout and System Tray (#590) 2025-10-30 13:38:57 -04:00
github-actions[bot]
4fe79dbe85 i18n: update source strings from codebase 2025-10-30 17:14:49 +00:00
bbedward
55d738e917 about: fix links 2025-10-30 13:13:29 -04:00
github-actions[bot]
986b07f4a9 i18n: update translations 2025-10-30 17:04:57 +00:00
github-actions[bot]
450c2e91ed i18n: update source strings from codebase 2025-10-30 17:04:47 +00:00
bbedward
4d06333624 about page: update for mango and sway 2025-10-30 13:04:10 -04:00
Tulip Blossom
fbe4122404 fix(dms-greeter,rpm): greeter user is supplied by sysusers and having manual user on the spec breaks it (#585)
* fix(dms-greeter,rpm): greeter user is supplied by sysusers and having manual user on the spec breaks it

This makes it so this RPM works fine on fedora 43, the greeter user
should be created and configured by systemd sysusers anyways

Signed-off-by: Tulip Blossom <tulilirockz@outlook.com>

* fix(dms-greeter): use systemd-tmpfiles to set up greeter directories

Signed-off-by: Tulip Blossom <tulilirockz@outlook.com>

* fix(rpm, dms-greeter): require systemd for tmpfiles macro

Signed-off-by: Tulip Blossom <tulilirockz@outlook.com>

---------

Signed-off-by: Tulip Blossom <tulilirockz@outlook.com>
2025-10-30 10:54:37 -04:00
bbedward
baf9b5e6f3 dwl: dont show empty tags 2025-10-30 10:50:35 -04:00
bbedward
c88fc20701 vpn: fix persistence
fixes #587
2025-10-30 09:33:50 -04:00
github-actions[bot]
b1078d6c73 i18n: update translations 2025-10-30 13:22:02 +00:00
bbedward
5033d10246 dwl: remove wlr-randr dependency 2025-10-30 09:21:20 -04:00
bbedward
986993a890 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-29 23:53:43 -04:00
bbedward
19b13a1e81 dwl: tag changes 2025-10-29 23:45:40 -04:00
bbedward
76637fab33 dwl: hide empty tags by default 2025-10-29 23:45:40 -04:00
github-actions[bot]
0a79d9a187 i18n: update translations 2025-10-30 03:39:57 +00:00
github-actions[bot]
36b3b3c7ae i18n: update source strings from codebase 2025-10-30 03:39:47 +00:00
bbedward
8caeca0c08 dwl: tag changes 2025-10-29 23:39:12 -04:00
bbedward
1c323f54ee dwl: hide empty tags by default 2025-10-29 23:07:15 -04:00
bbedward
7ed0b752a8 hyprland: some targeted improvements 2025-10-29 17:23:38 -04:00
bbedward
0569906f7c network: strip down legacy network service 2025-10-29 17:07:19 -04:00
bbedward
2a7cf187ad keyboard layout: remove polling on hyprland 2025-10-29 16:58:07 -04:00
github-actions[bot]
cc5b98a5d2 i18n: update source strings from codebase 2025-10-29 19:42:59 +00:00
bbedward
1478c92f49 matugen: fix wallpaperengine color generation 2025-10-29 15:41:10 -04:00
github-actions[bot]
e1785a1738 i18n: update translations 2025-10-29 19:10:16 +00:00
github-actions[bot]
44ebd2918c i18n: update source strings from codebase 2025-10-29 19:10:05 +00:00
bbedward
c87fa0de5e sway: add support for sway 2025-10-29 15:08:11 -04:00
bbedward
7b26692c8e dwl: support display scales 2025-10-29 13:45:22 -04:00
bbedward
b294e391e7 settings: don't overflow screen dimensions 2025-10-29 13:34:11 -04:00
bbedward
85f8e362e6 pam: try to avoid racey unlock states 2025-10-29 12:59:35 -04:00
github-actions[bot]
d68a6a1056 i18n: update translations 2025-10-29 16:42:00 +00:00
github-actions[bot]
3dae9c0639 i18n: update source strings from codebase 2025-10-29 16:41:52 +00:00
bbedward
aede6b064a dwl: add dwl/MangoWC support
- Requires dms api v12
- Tags/Workspace support
- MangoWC launcher logo
- dpms off/on support
- logout support
2025-10-29 12:39:31 -04:00
bbedward
76b168020c dash: fix IPC positioning 2025-10-29 10:42:39 -04:00
bbedward
5e36b1454a wallpaper: transition blurred wallpaper layer fixes #579 2025-10-29 09:26:03 -04:00
github-actions[bot]
bd35fbac4d i18n: update source strings from codebase 2025-10-29 13:19:15 +00:00
bbedward
e081ec19cc dankbar: cooldown timer on scrolling workspaces 2025-10-29 09:18:46 -04:00
github-actions[bot]
d870d8bad6 i18n: update translations 2025-10-29 13:12:54 +00:00
bbedward
20fd13c836 dankbar: scroll wheels to cycle apps and workspaces 2025-10-29 09:12:10 -04:00
github-actions[bot]
59f98b151d i18n: update source strings from codebase 2025-10-28 20:40:45 +00:00
bbedward
4ac1990c12 systray: fix icon fallback 2025-10-28 16:40:13 -04:00
bbedward
0a5105cc62 niri: simple blur rule 2025-10-28 12:49:49 -04:00
bbedward
a9f8b835ee notepad: use a mask over content area 2025-10-28 12:08:18 -04:00
bbedward
0109bd5bda Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-28 11:37:50 -04:00
bbedward
01dad64c6d notepad: fix mousearea width
fixes #569
2025-10-28 11:37:24 -04:00
bbedward
ee38f57f6d filebrowser: use NF icons 2025-10-28 11:36:18 -04:00
github-actions[bot]
6b163dcb5f Update VERSION to v0.2.5 (from DMS) 2025-10-28 15:29:32 +00:00
github-actions[bot]
baadbbc65a i18n: update source strings from codebase 2025-10-28 15:21:25 +00:00
bbedward
13a2813db9 calendar: parse event links from description 2025-10-28 11:20:42 -04:00
bbedward
cfa7d12dd3 matugen: auto re-load kitty config on color changes 2025-10-28 11:01:26 -04:00
bbedward
8bf23d820f sounds: fix search path to include generic data location
fixes #566
2025-10-28 10:42:01 -04:00
github-actions[bot]
3c7e903ace i18n: update source strings from codebase 2025-10-28 14:20:34 +00:00
bbedward
ee0e3aece9 color: hide picker instantly when spawning hyprpicker
fixes #575
2025-10-28 10:19:56 -04:00
bbedward
d7efd1b285 wallpaper: fix auto cycling initialization 2025-10-28 09:42:50 -04:00
github-actions[bot]
34f7a7ab18 i18n: update translations 2025-10-28 13:32:30 +00:00
bbedward
695eb0a401 i18n: add chinese traiditonal 2025-10-28 09:31:45 -04:00
github-actions[bot]
0d44b95a40 i18n: update translations 2025-10-28 12:55:57 +00:00
bbedward
116c421492 net: don't force singleActive on VPNs
fixes #574
2025-10-28 08:54:56 -04:00
github-actions[bot]
53507ef56b i18n: update translations 2025-10-28 00:21:15 +00:00
bbedward
3c049e031f niri: add workspace assignment helper 2025-10-27 20:20:34 -04:00
bbedward
b6688adb35 niri: more blur on overview 2025-10-27 17:54:53 -04:00
bbedward
b46fe28c05 niri: generate wpblur.kdl 2025-10-27 17:21:26 -04:00
bbedward
e7debdcf46 weather: switch to ip-api for auto location 2025-10-27 16:14:07 -04:00
bbedward
2c2930e876 proc: timeout to CLI helper 2025-10-27 16:13:10 -04:00
github-actions[bot]
ca294fc049 i18n: update translations 2025-10-27 19:38:34 +00:00
github-actions[bot]
86d1a40299 i18n: update source strings from codebase 2025-10-27 19:38:27 +00:00
bbedward
7a3884a633 wallpaper: fix per-monitor dankdash tab + allow matugen override
per-monitor

fixes #561
2025-10-27 15:36:07 -04:00
bbedward
7e5c6581c9 dsearch: rewrite to use CLI instead of api 2025-10-27 15:13:29 -04:00
github-actions[bot]
f17bbbd689 i18n: update source strings from codebase 2025-10-27 18:36:38 +00:00
bbedward
24b046e9d7 dankbar: fix app icon scaling fixes #568 2025-10-27 14:36:15 -04:00
github-actions[bot]
48a7d24c11 i18n: update source strings from codebase 2025-10-27 18:18:05 +00:00
bbedward
033f96a4b0 vpn: various state management fixes 2025-10-27 14:17:29 -04:00
bbedward
f0a1cb6525 icon: fix nerd font path 2025-10-27 11:44:17 -04:00
bbedward
db5782783b files: fix more icon map 2025-10-27 11:35:18 -04:00
bbedward
29022e260d file search: fix more icon mappings 2025-10-27 11:32:07 -04:00
bbedward
1e1f58d3ed fix archive map 2025-10-27 11:29:36 -04:00
github-actions[bot]
12389e2856 i18n: update source strings from codebase 2025-10-27 15:27:51 +00:00
bbedward
cde7427449 file search: add a bunch of nerd icon mappings 2025-10-27 11:27:15 -04:00
github-actions[bot]
42e7cb7b5f i18n: update translations 2025-10-27 15:00:04 +00:00
github-actions[bot]
d7992bc1f7 i18n: update source strings from codebase 2025-10-27 14:59:58 +00:00
bbedward
61c8549401 audio: strip file:// prefix for local dir sounds 2025-10-27 10:59:07 -04:00
bbedward
a284dcf61d i18n: add italian 2025-10-27 10:57:48 -04:00
bbedward
2e462b0899 spotlight: fix file search keyboard navi woes 2025-10-27 10:57:12 -04:00
bbedward
b79c66d59a plugins: fix listview scroll issues
fixes #564
2025-10-27 10:53:29 -04:00
Bruno Cesar Rocha
2f2020e7e2 fix: Load launcher plugin no-trigger settings. (#567)
getPluginTrigger() function only loaded the trigger setting but completely ignored the
noTrigger boolean setting.

When noTrigger was enabled and saved as:
- noTrigger: true
- trigger: ""

On reboot, the function would load trigger which was "", but since empty string is falsy
in the fallback expression, it would revert to plugin.trigger || "!" from the
plugin.json manifest, which is "=" for the Calculator plugin.
2025-10-27 09:55:15 -04:00
github-actions[bot]
b7e99c0d2b i18n: update translations 2025-10-27 13:50:04 +00:00
github-actions[bot]
2648848898 i18n: update source strings from codebase 2025-10-27 13:49:59 +00:00
bbedward
79b23ca829 spotlight: danksearch integration (indexed file search) 2025-10-27 09:49:17 -04:00
bbedward
0ac5b7bc87 workspaces: add desktop entries binding 2025-10-26 22:35:45 -04:00
bbedward
1d211e8474 dock: remove un-used mousearea 2025-10-26 22:32:33 -04:00
github-actions[bot]
1981a83e82 i18n: update translations 2025-10-26 14:19:04 +00:00
Massimo Branchini
cac071e7af filebrowser: use xdg paths (#555) 2025-10-26 10:18:28 -04:00
github-actions[bot]
c6efccd61c i18n: update source strings from codebase 2025-10-26 13:07:38 +00:00
ahoyiski
a90b00e5fe Added a Compact mode to the keyboard_layout_name widget. (#554)
* Added a Compact mode to the keyboard_layout_name widget.

* Added another root.currentLayout = "Unknown" that was missing.
2025-10-26 09:07:11 -04:00
github-actions[bot]
7863d03282 i18n: update source strings from codebase 2025-10-26 03:31:46 +00:00
bbedward
968606d781 filebrowser: improved file browser 2025-10-25 23:30:33 -04:00
bbedward
f7e8de2556 fix section 2025-10-25 18:39:29 -04:00
bbedward
17a8edc1ae greetd: disable hypr logo 2025-10-25 18:39:06 -04:00
github-actions[bot]
30dc63c801 Update VERSION to v0.2.4 (from DMS) 2025-10-25 22:33:32 +00:00
bbedward
8db7b8419a remove font check 2025-10-25 18:30:24 -04:00
bbedward
8c626b20e1 dankbar: fix center section spacers 2025-10-25 18:21:22 -04:00
github-actions[bot]
a8929c8046 i18n: update translations 2025-10-25 21:58:03 +00:00
bbedward
f8e4b5e958 remove font deps from copr 2025-10-25 17:57:20 -04:00
bbedward
58cae24157 fonts: bundle Inter + FiraCode Nerd
- remove all font dependencies
2025-10-25 17:53:08 -04:00
bbedward
bb4f5f37cc Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-25 16:16:16 -04:00
bbedward
237333941a font: bundle in material symbols rounded 2025-10-25 16:15:15 -04:00
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
311 changed files with 32510 additions and 9615 deletions

24
.githooks/pre-commit Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$HOOK_DIR/.." && pwd)"
cd "$REPO_ROOT"
if [[ -z "${POEDITOR_API_TOKEN:-}" ]] || [[ -z "${POEDITOR_PROJECT_ID:-}" ]]; then
exit 0
fi
if ! command -v python3 &>/dev/null; then
exit 0
fi
if ! python3 scripts/i18nsync.py check &>/dev/null; then
echo "Translations out of sync"
echo "run python3 scripts/i18nsync.py sync"
exit 1
fi
exit 0

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: [avengemedia]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: danklinux
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -24,6 +24,8 @@ assignees: ""
- [ ] niri
- [ ] Hyprland
- [ ] dwl (MangoWC)
- [ ] sway
- [ ] Other (specify)
## Distribution

View File

@@ -21,6 +21,8 @@ Is this feature specific to one compositor?
- [ ] All compositors
- [ ] niri
- [ ] Hyprland
- [ ] dwl (MangoWC)
- [ ] sway
## Proposed Solution

View File

@@ -10,6 +10,8 @@ assignees: ""
- [ ] niri
- [ ] Hyprland
- [ ] dwl (MangoWC)
- [ ] sway
- [ ] other
## Distribution

View File

@@ -1,11 +1,10 @@
name: DMS Copr Stable Release
on:
push:
tags:
- 'v*'
release:
types: [published]
workflow_run:
workflows: ["Create Release from DMS"]
types: [completed]
branches: [master]
workflow_dispatch:
inputs:
version:
@@ -16,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
@@ -27,20 +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 }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
echo "Using tag 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"
@@ -92,17 +86,16 @@ jobs:
BuildRequires: gzip
BuildRequires: wget
BuildRequires: systemd-rpm-macros
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
Requires: material-symbols-fonts
Requires: rsms-inter-fonts
Recommends: brightnessctl
Recommends: cava
Recommends: cliphist
Recommends: danksearch
Recommends: hyprpicker
Recommends: matugen
Recommends: wl-clipboard
@@ -179,18 +172,35 @@ 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 -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
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
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
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
pkill -USR1 -x dms >/dev/null 2>&1 || true
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%{_datadir}/quickshell/dms/
%{_userunitdir}/dms.service
%files -n dms-cli
%{_bindir}/dms

View File

@@ -1,189 +0,0 @@
name: POEditor Diff & Sync
on:
push:
branches: [ master ]
workflow_dispatch: {}
concurrency:
group: poeditor-sync
cancel-in-progress: false
jobs:
sync-translations:
runs-on: ubuntu-latest
timeout-minutes: 20
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: Extract source strings from codebase
env:
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
run: |
set -euo pipefail
echo "::group::Extracting strings from QML files"
python3 translations/extract_translations.py
echo "::endgroup::"
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
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"
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 "::warning::translations/en.json not found"
echo "source_changed=false" >> "$GITHUB_OUTPUT"
fi
echo "::endgroup::"
id: extract
- name: Commit and push source strings
if: steps.extract.outputs.source_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/en.json translations/template.json
git commit -m "i18n: update source strings from codebase"
for attempt in 1 2 3; do
if git push; then
echo "Successfully pushed source string 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
- 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"
)
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:"%an|%s (%h)" | grep -v "^github-actions\[bot\]|" | sed 's/^[^|]*|/- /' | head -50)
else
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
CHANGELOG=$(git log --oneline --pretty=format:"%an|%s (%h)" "${PREVIOUS_TAG}..${TAG}" | grep -v "^github-actions\[bot\]|" | sed 's/^[^|]*|/- /')
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

@@ -22,6 +22,57 @@ Singleton {
property string wallpaperLastPath: ""
property string profileLastPath: ""
property var fileBrowserSettings: ({
"wallpaper": {
"lastPath": "",
"viewMode": "grid",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"profile": {
"lastPath": "",
"viewMode": "grid",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"notepad_save": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"notepad_load": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"generic": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"default": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
}
})
Component.onCompleted: {
if (!isGreeterMode) {
loadCache()
@@ -43,6 +94,37 @@ Singleton {
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : ""
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : ""
if (cache.fileBrowserSettings !== undefined) {
fileBrowserSettings = cache.fileBrowserSettings
} else if (cache.fileBrowserViewMode !== undefined) {
fileBrowserSettings = {
"wallpaper": {
"lastPath": cache.wallpaperLastPath || "",
"viewMode": cache.fileBrowserViewMode || "grid",
"sortBy": cache.fileBrowserSortBy || "name",
"sortAscending": cache.fileBrowserSortAscending !== undefined ? cache.fileBrowserSortAscending : true,
"iconSizeIndex": cache.fileBrowserIconSizeIndex !== undefined ? cache.fileBrowserIconSizeIndex : 1,
"showSidebar": cache.fileBrowserShowSidebar !== undefined ? cache.fileBrowserShowSidebar : true
},
"profile": {
"lastPath": cache.profileLastPath || "",
"viewMode": cache.fileBrowserViewMode || "grid",
"sortBy": cache.fileBrowserSortBy || "name",
"sortAscending": cache.fileBrowserSortAscending !== undefined ? cache.fileBrowserSortAscending : true,
"iconSizeIndex": cache.fileBrowserIconSizeIndex !== undefined ? cache.fileBrowserIconSizeIndex : 1,
"showSidebar": cache.fileBrowserShowSidebar !== undefined ? cache.fileBrowserShowSidebar : true
},
"file": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
}
}
}
if (cache.configVersion === undefined) {
migrateFromUndefinedToV1(cache)
cleanupUnusedKeys()
@@ -62,18 +144,20 @@ Singleton {
cacheFile.setText(JSON.stringify({
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"fileBrowserSettings": fileBrowserSettings,
"configVersion": cacheConfigVersion
}, null, 2))
}
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() {
const validKeys = [
"wallpaperLastPath",
"profileLastPath",
"fileBrowserSettings",
"configVersion"
]
@@ -115,7 +199,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

@@ -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

@@ -9,20 +9,24 @@ Singleton {
id: root
property int defaultDebounceMs: 50
property var _procDebouncers: ({}) // id -> { timer, command, callback, waitMs }
property int defaultTimeoutMs: 10000
property var _procDebouncers: ({})
function runCommand(id, command, callback, debounceMs) {
function runCommand(id, command, callback, debounceMs, timeoutMs) {
const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs
let procId = id ? id : Math.random()
const timeout = (typeof timeoutMs === "number" && timeoutMs > 0) ? timeoutMs : defaultTimeoutMs
let procId = id ? id : Math.random()
const isRandomId = !id
if (!_procDebouncers[procId]) {
const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root)
t.triggered.connect(function() { _launchProc(procId) })
_procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait }
t.triggered.connect(function() { _launchProc(procId, isRandomId) })
_procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait, timeoutMs: timeout, isRandomId: isRandomId }
} else {
_procDebouncers[procId].command = command
_procDebouncers[procId].callback = callback
_procDebouncers[procId].waitMs = wait
_procDebouncers[procId].timeoutMs = timeout
}
const entry = _procDebouncers[procId]
@@ -30,41 +34,77 @@ Singleton {
entry.timer.restart()
}
function _launchProc(id) {
function _launchProc(id, isRandomId) {
const entry = _procDebouncers[id]
if (!entry) return
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root)
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
const timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root)
proc.stdout = out
proc.stderr = err
proc.command = entry.command
let capturedOut = ""
let capturedErr = ""
let exitSeen = false
let exitCodeValue = -1
let outSeen = false
let errSeen = false
let timedOut = false
timeoutTimer.interval = entry.timeoutMs
timeoutTimer.triggered.connect(function() {
if (!exitSeen) {
timedOut = true
proc.running = false
exitSeen = true
exitCodeValue = 124
maybeComplete()
}
})
out.streamFinished.connect(function() {
capturedOut = out.text || ""
outSeen = true
maybeComplete()
})
err.streamFinished.connect(function() {
capturedErr = err.text || ""
errSeen = true
maybeComplete()
})
proc.exited.connect(function(code) {
timeoutTimer.stop()
exitSeen = true
exitCodeValue = code
maybeComplete()
})
function maybeComplete() {
if (!exitSeen) return
if (!exitSeen || !outSeen || !errSeen) return
timeoutTimer.stop()
if (typeof entry.callback === "function") {
try { entry.callback(capturedOut, exitCodeValue) } catch (e) { console.warn("runCommand callback error:", e) }
}
try { proc.destroy() } catch (_) {}
try { timeoutTimer.destroy() } catch (_) {}
if (isRandomId || entry.isRandomId) {
Qt.callLater(function() {
if (_procDebouncers[id]) {
try { _procDebouncers[id].timer.destroy() } catch (_) {}
delete _procDebouncers[id]
}
})
}
}
proc.running = true
timeoutTimer.start()
}
}

View File

@@ -1,4 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
@@ -20,6 +21,7 @@ Singleton {
property bool isLightMode: false
property bool doNotDisturb: false
property bool isSwitchingMode: false
property string wallpaperPath: ""
property bool perMonitorWallpaper: false
@@ -41,6 +43,7 @@ Singleton {
property bool nightModeEnabled: false
property int nightModeTemperature: 4500
property int nightModeHighTemperature: 6500
property bool nightModeAutoEnabled: false
property string nightModeAutoMode: "time"
property int nightModeStartHour: 18
@@ -57,6 +60,9 @@ Singleton {
property bool showThirdPartyPlugins: false
property string launchPrefix: ""
property string lastBrightnessDevice: ""
property var brightnessExponentialDevices: ({})
property var brightnessUserSetValues: ({})
property var brightnessExponentValues: ({})
property int selectedGpuIndex: 0
property bool nvidiaGpuTempEnabled: false
@@ -82,7 +88,21 @@ Singleton {
if (content && content.trim()) {
var settings = JSON.parse(content)
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : ""
if (settings.wallpaperPath && settings.wallpaperPath.startsWith("we:")) {
console.warn("WallpaperEngine wallpaper detected, resetting wallpaper")
wallpaperPath = ""
Quickshell.execDetached([
"notify-send",
"-u", "critical",
"-a", "DMS",
"-i", "dialog-warning",
"WallpaperEngine Support Moved",
"WallpaperEngine support has been moved to a plugin. Please enable the Linux Wallpaper Engine plugin in Settings → Plugins to continue using WallpaperEngine."
])
} else {
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : ""
}
perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false
monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {}
perModeWallpaper = settings.perModeWallpaper !== undefined ? settings.perModeWallpaper : false
@@ -90,9 +110,13 @@ Singleton {
wallpaperPathDark = settings.wallpaperPathDark !== undefined ? settings.wallpaperPathDark : ""
monitorWallpapersLight = settings.monitorWallpapersLight !== undefined ? settings.monitorWallpapersLight : {}
monitorWallpapersDark = settings.monitorWallpapersDark !== undefined ? settings.monitorWallpapersDark : {}
brightnessExponentialDevices = settings.brightnessExponentialDevices !== undefined ? settings.brightnessExponentialDevices : (settings.brightnessLogarithmicDevices || {})
brightnessUserSetValues = settings.brightnessUserSetValues !== undefined ? settings.brightnessUserSetValues : {}
brightnessExponentValues = settings.brightnessExponentValues !== undefined ? settings.brightnessExponentValues : {}
doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
nightModeHighTemperature = settings.nightModeHighTemperature !== undefined ? settings.nightModeHighTemperature : 6500
nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false
nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time"
if (settings.nightModeStartTime !== undefined) {
@@ -144,6 +168,10 @@ Singleton {
Theme.generateSystemThemesFromCurrentTheme()
}
}
if (typeof WallpaperCyclingService !== "undefined") {
WallpaperCyclingService.updateCyclingState()
}
}
} catch (e) {
@@ -151,7 +179,8 @@ Singleton {
}
function saveSettings() {
if (isGreeterMode) return
if (isGreeterMode)
return
settingsFile.setText(JSON.stringify({
"isLightMode": isLightMode,
"wallpaperPath": wallpaperPath,
@@ -162,9 +191,13 @@ Singleton {
"wallpaperPathDark": wallpaperPathDark,
"monitorWallpapersLight": monitorWallpapersLight,
"monitorWallpapersDark": monitorWallpapersDark,
"brightnessExponentialDevices": brightnessExponentialDevices,
"brightnessUserSetValues": brightnessUserSetValues,
"brightnessExponentValues": brightnessExponentValues,
"doNotDisturb": doNotDisturb,
"nightModeEnabled": nightModeEnabled,
"nightModeTemperature": nightModeTemperature,
"nightModeHighTemperature": nightModeHighTemperature,
"nightModeAutoEnabled": nightModeAutoEnabled,
"nightModeAutoMode": nightModeAutoMode,
"nightModeStartHour": nightModeStartHour,
@@ -196,40 +229,40 @@ 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)
SettingsData.set("acMonitorTimeout", settings.acMonitorTimeout)
}
if (settings.acLockTimeout !== undefined) {
SettingsData.setAcLockTimeout(settings.acLockTimeout)
SettingsData.set("acLockTimeout", settings.acLockTimeout)
}
if (settings.acSuspendTimeout !== undefined) {
SettingsData.setAcSuspendTimeout(settings.acSuspendTimeout)
SettingsData.set("acSuspendTimeout", settings.acSuspendTimeout)
}
if (settings.acHibernateTimeout !== undefined) {
SettingsData.setAcHibernateTimeout(settings.acHibernateTimeout)
SettingsData.set("acHibernateTimeout", settings.acHibernateTimeout)
}
if (settings.batteryMonitorTimeout !== undefined) {
SettingsData.setBatteryMonitorTimeout(settings.batteryMonitorTimeout)
SettingsData.set("batteryMonitorTimeout", settings.batteryMonitorTimeout)
}
if (settings.batteryLockTimeout !== undefined) {
SettingsData.setBatteryLockTimeout(settings.batteryLockTimeout)
SettingsData.set("batteryLockTimeout", settings.batteryLockTimeout)
}
if (settings.batterySuspendTimeout !== undefined) {
SettingsData.setBatterySuspendTimeout(settings.batterySuspendTimeout)
SettingsData.set("batterySuspendTimeout", settings.batterySuspendTimeout)
}
if (settings.batteryHibernateTimeout !== undefined) {
SettingsData.setBatteryHibernateTimeout(settings.batteryHibernateTimeout)
SettingsData.set("batteryHibernateTimeout", settings.batteryHibernateTimeout)
}
if (settings.lockBeforeSuspend !== undefined) {
SettingsData.setLockBeforeSuspend(settings.lockBeforeSuspend)
SettingsData.set("lockBeforeSuspend", settings.lockBeforeSuspend)
}
if (settings.loginctlLockIntegration !== undefined) {
SettingsData.setLoginctlLockIntegration(settings.loginctlLockIntegration)
SettingsData.set("loginctlLockIntegration", settings.loginctlLockIntegration)
}
if (settings.launchPrefix !== undefined) {
SettingsData.setLaunchPrefix(settings.launchPrefix)
SettingsData.set("launchPrefix", settings.launchPrefix)
}
}
if (typeof CacheData !== "undefined") {
@@ -244,23 +277,12 @@ Singleton {
}
function cleanupUnusedKeys() {
const validKeys = [
"isLightMode", "wallpaperPath", "perMonitorWallpaper", "monitorWallpapers", "perModeWallpaper",
"wallpaperPathLight", "wallpaperPathDark", "monitorWallpapersLight",
"monitorWallpapersDark", "doNotDisturb", "nightModeEnabled",
"nightModeTemperature", "nightModeAutoEnabled", "nightModeAutoMode",
"nightModeStartHour", "nightModeStartMinute", "nightModeEndHour",
"nightModeEndMinute", "latitude", "longitude", "nightModeUseIPLocation", "nightModeLocationProvider",
"pinnedApps", "selectedGpuIndex", "nvidiaGpuTempEnabled",
"nonNvidiaGpuTempEnabled", "enabledGpuPciIds", "wallpaperCyclingEnabled",
"wallpaperCyclingMode", "wallpaperCyclingInterval", "wallpaperCyclingTime",
"monitorCyclingSettings", "lastBrightnessDevice", "launchPrefix", "wallpaperTransition",
"includedTransitions", "recentColors", "showThirdPartyPlugins", "configVersion"
]
const validKeys = ["isLightMode", "wallpaperPath", "perMonitorWallpaper", "monitorWallpapers", "perModeWallpaper", "wallpaperPathLight", "wallpaperPathDark", "monitorWallpapersLight", "monitorWallpapersDark", "doNotDisturb", "nightModeEnabled", "nightModeTemperature", "nightModeHighTemperature", "nightModeAutoEnabled", "nightModeAutoMode", "nightModeStartHour", "nightModeStartMinute", "nightModeEndHour", "nightModeEndMinute", "latitude", "longitude", "nightModeUseIPLocation", "nightModeLocationProvider", "pinnedApps", "selectedGpuIndex", "nvidiaGpuTempEnabled", "nonNvidiaGpuTempEnabled", "enabledGpuPciIds", "wallpaperCyclingEnabled", "wallpaperCyclingMode", "wallpaperCyclingInterval", "wallpaperCyclingTime", "monitorCyclingSettings", "lastBrightnessDevice", "brightnessExponentialDevices", "brightnessUserSetValues", "launchPrefix", "wallpaperTransition", "includedTransitions", "recentColors", "showThirdPartyPlugins", "configVersion"]
try {
const content = settingsFile.text()
if (!content || !content.trim()) return
if (!content || !content.trim())
return
const settings = JSON.parse(content)
let needsSave = false
@@ -282,9 +304,11 @@ Singleton {
}
function setLightMode(lightMode) {
isSwitchingMode = true
isLightMode = lightMode
syncWallpaperForCurrentMode()
saveSettings()
Qt.callLater(() => { isSwitchingMode = false })
}
function setDoNotDisturb(enabled) {
@@ -425,10 +449,13 @@ Singleton {
saveSettings()
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") {
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined" && typeof SettingsData !== "undefined") {
var screens = Quickshell.screens
if (screens.length > 0 && screenName === screens[0].name) {
Theme.generateSystemThemesFromCurrentTheme()
if (screens.length > 0) {
var targetMonitor = (SettingsData.matugenTargetMonitor && SettingsData.matugenTargetMonitor !== "") ? SettingsData.matugenTargetMonitor : screens[0].name
if (screenName === targetMonitor) {
Theme.generateSystemThemesFromCurrentTheme()
}
}
}
}
@@ -461,7 +488,12 @@ Singleton {
function setMonitorCyclingEnabled(screenName, enabled) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
newSettings[screenName] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].enabled = enabled
monitorCyclingSettings = newSettings
@@ -471,7 +503,12 @@ Singleton {
function setMonitorCyclingMode(screenName, mode) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
newSettings[screenName] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].mode = mode
monitorCyclingSettings = newSettings
@@ -481,7 +518,12 @@ Singleton {
function setMonitorCyclingInterval(screenName, interval) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
newSettings[screenName] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].interval = interval
monitorCyclingSettings = newSettings
@@ -491,7 +533,12 @@ Singleton {
function setMonitorCyclingTime(screenName, time) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
newSettings[screenName] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].time = time
monitorCyclingSettings = newSettings
@@ -508,6 +555,11 @@ Singleton {
saveSettings()
}
function setNightModeHighTemperature(temperature) {
nightModeHighTemperature = temperature
saveSettings()
}
function setNightModeAutoEnabled(enabled) {
console.log("SessionData: Setting nightModeAutoEnabled to", enabled)
nightModeAutoEnabled = enabled
@@ -592,7 +644,8 @@ Singleton {
let recent = recentColors.slice()
recent = recent.filter(c => c !== colorStr)
recent.unshift(colorStr)
if (recent.length > 5) recent = recent.slice(0, 5)
if (recent.length > 5)
recent = recent.slice(0, 5)
recentColors = recent
saveSettings()
}
@@ -612,6 +665,52 @@ Singleton {
saveSettings()
}
function setBrightnessExponential(deviceName, enabled) {
var newSettings = Object.assign({}, brightnessExponentialDevices)
if (enabled) {
newSettings[deviceName] = true
} else {
delete newSettings[deviceName]
}
brightnessExponentialDevices = newSettings
saveSettings()
if (typeof DisplayService !== "undefined") {
DisplayService.updateDeviceBrightnessDisplay(deviceName)
}
}
function getBrightnessExponential(deviceName) {
return brightnessExponentialDevices[deviceName] === true
}
function setBrightnessUserSetValue(deviceName, value) {
var newValues = Object.assign({}, brightnessUserSetValues)
newValues[deviceName] = value
brightnessUserSetValues = newValues
saveSettings()
}
function getBrightnessUserSetValue(deviceName) {
return brightnessUserSetValues[deviceName]
}
function setBrightnessExponent(deviceName, exponent) {
var newValues = Object.assign({}, brightnessExponentValues)
if (exponent !== undefined && exponent !== null) {
newValues[deviceName] = exponent
} else {
delete newValues[deviceName]
}
brightnessExponentValues = newValues
saveSettings()
}
function getBrightnessExponent(deviceName) {
const value = brightnessExponentValues[deviceName]
return value !== undefined ? value : 1.2
}
function setSelectedGpuIndex(index) {
selectedGpuIndex = index
saveSettings()
@@ -633,7 +732,8 @@ Singleton {
}
function syncWallpaperForCurrentMode() {
if (!perModeWallpaper) return
if (!perModeWallpaper)
return
if (perMonitorWallpaper) {
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
@@ -652,10 +752,10 @@ Singleton {
function getMonitorCyclingSettings(screenName) {
return monitorCyclingSettings[screenName] || {
enabled: false,
mode: "interval",
interval: 300,
time: "06:00"
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
@@ -707,7 +807,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()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ import Quickshell.Io
import Quickshell.Services.UPower
import qs.Common
import qs.Services
import qs.Modules.Greetd
import "StockThemes.js" as StockThemes
Singleton {
@@ -19,7 +20,8 @@ Singleton {
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
readonly property real popupDistance: {
if (typeof SettingsData === "undefined") return 4
if (typeof SettingsData === "undefined")
return 4
return SettingsData.popupGapsAuto ? Math.max(4, SettingsData.dankBarSpacing) : SettingsData.popupGapsManual
}
@@ -29,41 +31,16 @@ Singleton {
property bool colorsFileLoadFailed: false
readonly property string dynamic: "dynamic"
readonly property string custom : "custom"
readonly property string custom: "custom"
readonly property string homeDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation))
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
readonly property string wallpaperPath: {
if (typeof SessionData === "undefined") return ""
if (typeof SessionData === "undefined")
return ""
if (SessionData.perMonitorWallpaper) {
var screens = Quickshell.screens
if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
var wallpaperPath = firstMonitorWallpaper || SessionData.wallpaperPath
if (wallpaperPath && wallpaperPath.startsWith("we:")) {
return stateDir + "/we_screenshots/" + wallpaperPath.substring(3) + ".jpg"
}
return wallpaperPath
}
}
var wallpaperPath = SessionData.wallpaperPath
var screens = Quickshell.screens
if (screens.length > 0 && wallpaperPath && wallpaperPath.startsWith("we:")) {
return stateDir + "/we_screenshots/" + wallpaperPath.substring(3) + ".jpg"
}
return wallpaperPath
}
readonly property string rawWallpaperPath: {
if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens
if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
@@ -73,6 +50,34 @@ Singleton {
return SessionData.wallpaperPath
}
readonly property string rawWallpaperPath: {
if (typeof SessionData === "undefined")
return ""
if (SessionData.perMonitorWallpaper) {
var screens = Quickshell.screens
if (screens.length > 0) {
var targetMonitor = (typeof SettingsData !== "undefined" && SettingsData.matugenTargetMonitor && SettingsData.matugenTargetMonitor !== "") ? SettingsData.matugenTargetMonitor : screens[0].name
var targetMonitorExists = false
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === targetMonitor) {
targetMonitorExists = true
break
}
}
if (!targetMonitorExists) {
targetMonitor = screens[0].name
}
var targetMonitorWallpaper = SessionData.getMonitorWallpaper(targetMonitor)
return targetMonitorWallpaper || SessionData.wallpaperPath
}
}
return SessionData.wallpaperPath
}
property bool matugenAvailable: false
property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false
@@ -84,59 +89,56 @@ Singleton {
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir])
Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
matugenAvailable = (code === 0) && !envDisableMatugen
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode) {
return
}
if (!matugenAvailable || isGreeterMode) {
return
}
if (colorsFileLoadFailed && currentTheme === dynamic && wallpaperPath) {
console.log("Theme: Matugen now available, regenerating colors for dynamic theme")
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
return
}
if (colorsFileLoadFailed && currentTheme === dynamic && rawWallpaperPath) {
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"
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (rawWallpaperPath.startsWith("#")) {
setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType)
}
return
}
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
if (currentTheme === dynamic) {
if (wallpaperPath) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
}
} else {
let primaryColor
let matugenType
if (currentTheme === "custom") {
if (customThemeData && customThemeData.primary) {
primaryColor = customThemeData.primary
matugenType = customThemeData.matugen_type
}
} else {
primaryColor = currentThemeData.primary
matugenType = currentThemeData.matugen_type
}
if (currentTheme === dynamic) {
if (rawWallpaperPath) {
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (rawWallpaperPath.startsWith("#")) {
setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType)
}
}
} else {
let primaryColor
let matugenType
if (currentTheme === "custom") {
if (customThemeData && customThemeData.primary) {
primaryColor = customThemeData.primary
matugenType = customThemeData.matugen_type
}
} else {
primaryColor = currentThemeData.primary
matugenType = currentThemeData.matugen_type
}
if (primaryColor) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
}
}
}, 0)
if (primaryColor) {
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
}
}
}, 0)
if (typeof SessionData !== "undefined") {
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
}
@@ -194,22 +196,60 @@ Singleton {
}
}
readonly property var availableMatugenSchemes: [
({ "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.") })
]
readonly property var availableMatugenSchemes: [({
"value": "scheme-tonal-spot",
"label": "Tonal Spot",
"description": I18n.tr("Balanced palette with focused accents (default).")
}),
({
"value": "scheme-vibrant",
"label": "Vibrant",
"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) {
const schemes = availableMatugenSchemes
for (let i = 0; i < schemes.length; i++) {
for (var i = 0; i < schemes.length; i++) {
if (schemes[i].value === value)
return schemes[i]
}
@@ -220,12 +260,7 @@ Singleton {
property color primaryText: currentThemeData.primaryText
property color primaryContainer: currentThemeData.primaryContainer
property color secondary: currentThemeData.secondary
property color surface: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.background
}
return currentThemeData.surface
}
property color surface: currentThemeData.surface
property color surfaceText: currentThemeData.surfaceText
property color surfaceVariant: currentThemeData.surfaceVariant
property color surfaceVariantText: currentThemeData.surfaceVariantText
@@ -234,24 +269,9 @@ Singleton {
property color backgroundText: currentThemeData.backgroundText
property color outline: currentThemeData.outline
property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6)
property color surfaceContainer: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.surface
}
return currentThemeData.surfaceContainer
}
property color surfaceContainerHigh: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.surfaceContainer
}
return currentThemeData.surfaceContainerHigh
}
property color surfaceContainerHighest: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.surfaceContainerHigh
}
return currentThemeData.surfaceContainerHighest
}
property color surfaceContainer: currentThemeData.surfaceContainer
property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh
property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest
property color onSurface: surfaceText
property color onSurfaceVariant: surfaceVariantText
@@ -296,13 +316,37 @@ Singleton {
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
readonly property var animationDurations: [
{ shorter: 0, short: 0, medium: 0, long: 0, extraLong: 0 },
{ shorter: 50, short: 75, medium: 150, long: 250, extraLong: 500 },
{ shorter: 100, short: 150, medium: 300, long: 500, extraLong: 1000 },
{ shorter: 150, short: 225, medium: 450, long: 750, extraLong: 1500 },
{ shorter: 200, short: 300, medium: 600, long: 1000, extraLong: 2000 }
]
readonly property var animationDurations: [{
"shorter": 0,
"short": 0,
"medium": 0,
"long": 0,
"extraLong": 0
}, {
"shorter": 50,
"short": 75,
"medium": 150,
"long": 250,
"extraLong": 500
}, {
"shorter": 100,
"short": 150,
"medium": 300,
"long": 500,
"extraLong": 1000
}, {
"shorter": 150,
"short": 225,
"medium": 450,
"long": 750,
"extraLong": 1500
}, {
"shorter": 200,
"short": 300,
"medium": 600,
"long": 1000,
"extraLong": 2000
}]
readonly property int currentAnimationSpeed: typeof SettingsData !== "undefined" ? SettingsData.animationSpeed : SettingsData.AnimationSpeed.Short
readonly property var currentDurations: animationDurations[currentAnimationSpeed] || animationDurations[SettingsData.AnimationSpeed.Short]
@@ -335,12 +379,13 @@ Singleton {
}
readonly property int currentAnimationBaseDuration: {
if (typeof SettingsData === "undefined") return 500
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
}
@@ -370,7 +415,12 @@ Singleton {
}
}
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
property real cornerRadius: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.cornerRadius
}
return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
}
property real spacingXS: 4
property real spacingS: 8
property real spacingM: 12
@@ -421,7 +471,7 @@ Singleton {
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
SettingsData.setTheme(currentTheme)
SettingsData.set("currentThemeName", currentTheme)
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme()
@@ -485,20 +535,40 @@ Singleton {
function getCatppuccinColor(variantName) {
const catColors = {
"cat-rosewater": "#f5e0dc", "cat-flamingo": "#f2cdcd", "cat-pink": "#f5c2e7", "cat-mauve": "#cba6f7",
"cat-red": "#f38ba8", "cat-maroon": "#eba0ac", "cat-peach": "#fab387", "cat-yellow": "#f9e2af",
"cat-green": "#a6e3a1", "cat-teal": "#94e2d5", "cat-sky": "#89dceb", "cat-sapphire": "#74c7ec",
"cat-blue": "#89b4fa", "cat-lavender": "#b4befe"
"cat-rosewater": "#f5e0dc",
"cat-flamingo": "#f2cdcd",
"cat-pink": "#f5c2e7",
"cat-mauve": "#cba6f7",
"cat-red": "#f38ba8",
"cat-maroon": "#eba0ac",
"cat-peach": "#fab387",
"cat-yellow": "#f9e2af",
"cat-green": "#a6e3a1",
"cat-teal": "#94e2d5",
"cat-sky": "#89dceb",
"cat-sapphire": "#74c7ec",
"cat-blue": "#89b4fa",
"cat-lavender": "#b4befe"
}
return catColors[variantName] || "#cba6f7"
}
function getCatppuccinVariantName(variantName) {
const catNames = {
"cat-rosewater": "Rosewater", "cat-flamingo": "Flamingo", "cat-pink": "Pink", "cat-mauve": "Mauve",
"cat-red": "Red", "cat-maroon": "Maroon", "cat-peach": "Peach", "cat-yellow": "Yellow",
"cat-green": "Green", "cat-teal": "Teal", "cat-sky": "Sky", "cat-sapphire": "Sapphire",
"cat-blue": "Blue", "cat-lavender": "Lavender"
"cat-rosewater": "Rosewater",
"cat-flamingo": "Flamingo",
"cat-pink": "Pink",
"cat-mauve": "Mauve",
"cat-red": "Red",
"cat-maroon": "Maroon",
"cat-peach": "Peach",
"cat-yellow": "Yellow",
"cat-green": "Green",
"cat-teal": "Teal",
"cat-sky": "Sky",
"cat-sapphire": "Sapphire",
"cat-blue": "Blue",
"cat-lavender": "Lavender"
}
return catNames[variantName] || "Unknown"
}
@@ -523,13 +593,6 @@ Singleton {
readonly property var _availableThemeNames: StockThemes.getAllThemeNames()
property string currentThemeName: currentTheme
function popupBackground() {
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, popupTransparency)
}
function contentBackground() {
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, popupTransparency)
}
function panelBackground() {
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, panelTransparency)
@@ -541,14 +604,14 @@ Singleton {
const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"
switch (colorMode) {
case "s":
return surface
return surface
case "sc":
return surfaceContainer
return surfaceContainer
case "sch":
return surfaceContainerHigh
return surfaceContainerHigh
case "sth":
default:
return surfaceTextHover
return surfaceTextHover
}
}
@@ -562,24 +625,17 @@ Singleton {
const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"
switch (colorMode) {
case "s":
return Qt.rgba(surface.r, surface.g, surface.b, widgetTransparency)
return Qt.rgba(surface.r, surface.g, surface.b, widgetTransparency)
case "sc":
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
case "sch":
return Qt.rgba(surfaceContainerHigh.r, surfaceContainerHigh.g, surfaceContainerHigh.b, widgetTransparency)
return Qt.rgba(surfaceContainerHigh.r, surfaceContainerHigh.g, surfaceContainerHigh.b, widgetTransparency)
case "sth":
default:
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency)
}
}
function getPopupBackgroundAlpha() {
return popupTransparency
}
function getContentBackgroundAlpha() {
return popupTransparency
}
function isColorDark(c) {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
@@ -593,8 +649,10 @@ Singleton {
function barTextSize(barThickness) {
const scale = barThickness / 48
const dankBarScale = (typeof SettingsData !== "undefined" ? SettingsData.dankBarFontScale : 1.0)
if (scale <= 0.75) return fontSizeSmall * 0.9 * dankBarScale
if (scale >= 1.25) return fontSizeMedium * dankBarScale
if (scale <= 0.75)
return fontSizeSmall * 0.9 * dankBarScale
if (scale >= 1.25)
return fontSizeMedium * dankBarScale
return fontSizeSmall * dankBarScale
}
@@ -688,7 +746,6 @@ Singleton {
}
}
function onLightModeChanged() {
if (currentTheme === "custom" && customThemeFileView.path) {
customThemeFileView.reload()
@@ -701,7 +758,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()
@@ -713,7 +770,6 @@ Singleton {
"mode": isLight ? "light" : "dark",
"iconTheme": iconTheme || "System Default",
"matugenType": matugenType || "scheme-tonal-spot",
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc",
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
}
@@ -723,16 +779,8 @@ 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}' '${syncModeWithPortal}' --run`
]
} else {
console.log("Theme: Starting matugen worker")
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"]
}
console.log("Theme: Starting matugen worker")
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"]
systemThemeGenerator.running = true
}
@@ -745,14 +793,14 @@ Singleton {
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
if (currentTheme === dynamic) {
if (!wallpaperPath) {
if (!rawWallpaperPath) {
return
}
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
if (rawWallpaperPath.startsWith("#")) {
setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType)
}
} else {
let primaryColor
@@ -787,16 +835,16 @@ Singleton {
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"
Proc.runCommand("gtkApplier", [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo("GTK colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply GTK colors")
}
}
})
if (exitCode === 0) {
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo("GTK colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply GTK colors")
}
}
})
}
function applyQtColors() {
@@ -808,32 +856,42 @@ Singleton {
}
Proc.runCommand("qtApplier", [shellDir + "/scripts/qt.sh", configDir], (output, exitCode) => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Qt colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply Qt colors")
}
}
})
if (exitCode === 0) {
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Qt colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply Qt colors")
}
}
})
}
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
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
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
}
}
@@ -852,40 +910,48 @@ Singleton {
}
function invertHex(hex) {
hex = hex.replace('#', '');
hex = hex.replace('#', '')
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
return hex;
return hex
}
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const r = parseInt(hex.substr(0, 2), 16)
const g = parseInt(hex.substr(2, 2), 16)
const b = parseInt(hex.substr(4, 2), 16)
const invR = (255 - r).toString(16).padStart(2, '0');
const invG = (255 - g).toString(16).padStart(2, '0');
const invB = (255 - b).toString(16).padStart(2, '0');
const invR = (255 - r).toString(16).padStart(2, '0')
const invG = (255 - g).toString(16).padStart(2, '0')
const invB = (255 - b).toString(16).padStart(2, '0')
return `#${invR}${invG}${invB}`;
return `#${invR}${invG}${invB}`
}
property string baseLogoColor: {
if (typeof SettingsData === "undefined") return ""
if (typeof SettingsData === "undefined")
return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
if (!colorOverride || colorOverride === "")
return ""
if (colorOverride === "primary")
return primary
if (colorOverride === "surface")
return surfaceText
return colorOverride
}
property string effectiveLogoColor: {
if (typeof SettingsData === "undefined") return ""
if (typeof SettingsData === "undefined")
return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (!colorOverride || colorOverride === "")
return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
if (colorOverride === "primary")
return primary
if (colorOverride === "surface")
return surfaceText
if (!SettingsData.launcherLogoColorInvertOnMode) {
return colorOverride
@@ -898,8 +964,6 @@ Singleton {
return colorOverride
}
Process {
id: systemThemeGenerator
running: false
@@ -908,7 +972,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()
@@ -956,9 +1020,7 @@ Singleton {
id: dynamicColorsFileView
path: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
const colorsPath = SessionData.isGreeterMode
? greetCfgDir + "/colors.json"
: stateDir + "/dms-colors.json"
const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json"
return colorsPath
}
watchChanges: currentTheme === dynamic && !SessionData.isGreeterMode
@@ -983,7 +1045,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()
}
@@ -997,10 +1059,10 @@ 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) {
if (!isGreeterMode && matugenAvailable && rawWallpaperPath) {
console.log("Theme: Matugen available, triggering immediate regeneration")
generateSystemThemesFromCurrentTheme()
}

56
Common/settings/Lists.qml Normal file
View File

@@ -0,0 +1,56 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
Singleton {
id: root
function init(leftModel, centerModel, rightModel, left, center, right) {
const dummy = {
widgetId: "dummy",
enabled: true,
size: 20,
selectedGpuIndex: 0,
pciId: "",
mountPath: "/",
minimumWidth: true,
showSwap: false
}
leftModel.append(dummy)
centerModel.append(dummy)
rightModel.append(dummy)
update(leftModel, left)
update(centerModel, center)
update(rightModel, right)
}
function update(model, order) {
model.clear()
for (var i = 0; i < order.length; i++) {
var widgetId = typeof order[i] === "string" ? order[i] : order[i].id
var enabled = typeof order[i] === "string" ? true : order[i].enabled
var size = typeof order[i] === "string" ? undefined : order[i].size
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
var minimumWidth = typeof order[i] === "string" ? undefined : order[i].minimumWidth
var showSwap = typeof order[i] === "string" ? undefined : order[i].showSwap
var item = {
widgetId: widgetId,
enabled: enabled
}
if (size !== undefined) item.size = size
if (selectedGpuIndex !== undefined) item.selectedGpuIndex = selectedGpuIndex
if (pciId !== undefined) item.pciId = pciId
if (mountPath !== undefined) item.mountPath = mountPath
if (minimumWidth !== undefined) item.minimumWidth = minimumWidth
if (showSwap !== undefined) item.showSwap = showSwap
model.append(item)
}
}
}

View File

@@ -0,0 +1,131 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property var settingsRoot: null
function detectIcons() {
systemDefaultDetectionProcess.running = true
}
function detectQtTools() {
qtToolsDetectionProcess.running = true
}
function detectFprintd() {
fprintdDetectionProcess.running = true
}
function checkPluginSettings() {
pluginSettingsCheckProcess.running = true
}
function checkDefaultSettings() {
defaultSettingsCheckProcess.running = true
}
property var systemDefaultDetectionProcess: Process {
command: ["sh", "-c", "gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed \"s/'//g\" || echo ''"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
if (exitCode === 0 && stdout && stdout.length > 0) {
settingsRoot.systemDefaultIconTheme = stdout.trim();
} else {
settingsRoot.systemDefaultIconTheme = "";
}
iconThemeDetectionProcess.running = true;
}
}
property var iconThemeDetectionProcess: Process {
command: ["sh", "-c", "find /usr/share/icons ~/.local/share/icons ~/.icons -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | grep -v '^icons$' | sort -u"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (!settingsRoot) return
var detectedThemes = ["System Default"]
if (text && text.trim()) {
var themes = text.trim().split('\n')
for (var i = 0; i < themes.length; i++) {
var theme = themes[i].trim()
if (theme && theme !== "" && theme !== "default" && theme !== "hicolor" && theme !== "locolor") {
detectedThemes.push(theme)
}
}
}
settingsRoot.availableIconThemes = detectedThemes
}
}
}
property var qtToolsDetectionProcess: Process {
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (!settingsRoot) return;
if (text && text.trim()) {
var lines = text.trim().split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith('qt5ct:')) {
settingsRoot.qt5ctAvailable = line.split(':')[1] === 'true';
} else if (line.startsWith('qt6ct:')) {
settingsRoot.qt6ctAvailable = line.split(':')[1] === 'true';
} else if (line.startsWith('gtk:')) {
settingsRoot.gtkAvailable = line.split(':')[1] === 'true';
}
}
}
}
}
}
property var defaultSettingsCheckProcess: Process {
command: ["sh", "-c", "CONFIG_DIR=\"" + (settingsRoot?._configDir || "") + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
if (exitCode === 0) {
console.info("Copied default-settings.json to settings.json");
if (settingsRoot.settingsFile) {
settingsRoot.settingsFile.reload();
}
} else {
if (typeof ThemeApplier !== "undefined") {
ThemeApplier.applyStoredTheme(settingsRoot);
}
}
}
}
property var fprintdDetectionProcess: Process {
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
settingsRoot.fprintdAvailable = (exitCode === 0);
}
}
property var pluginSettingsCheckProcess: Process {
command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""]
running: false
onExited: function(exitCode) {
if (!settingsRoot) return;
settingsRoot.pluginSettingsFileExists = (exitCode === 0);
}
}
}

View File

@@ -0,0 +1,232 @@
.pragma library
function percentToUnit(v) {
if (v === undefined || v === null) return undefined;
return v > 1 ? v / 100 : v;
}
var SPEC = {
currentThemeName: { def: "blue", onChange: "applyStoredTheme" },
customThemeFile: { def: "" },
matugenScheme: { def: "scheme-tonal-spot", onChange: "regenSystemThemes" },
runUserMatugenTemplates: { def: true, onChange: "regenSystemThemes" },
matugenTargetMonitor: { def: "", onChange: "regenSystemThemes" },
dankBarTransparency: { def: 1.0, coerce: percentToUnit, migrate: ["topBarTransparency"] },
dankBarWidgetTransparency: { def: 1.0, coerce: percentToUnit, migrate: ["topBarWidgetTransparency"] },
popupTransparency: { def: 1.0, coerce: percentToUnit },
dockTransparency: { def: 1.0, coerce: percentToUnit },
widgetBackgroundColor: { def: "sch" },
cornerRadius: { def: 12, onChange: "updateNiriLayout" },
use24HourClock: { def: true },
showSeconds: { def: false },
useFahrenheit: { def: false },
nightModeEnabled: { def: false },
animationSpeed: { def: 1 },
customAnimationDuration: { def: 500 },
wallpaperFillMode: { def: "Fill" },
blurredWallpaperLayer: { def: false },
blurWallpaperOnOverview: { def: false },
showLauncherButton: { def: true },
showWorkspaceSwitcher: { def: true },
showFocusedWindow: { def: true },
showWeather: { def: true },
showMusic: { def: true },
showClipboard: { def: true },
showCpuUsage: { def: true },
showMemUsage: { def: true },
showCpuTemp: { def: true },
showGpuTemp: { def: true },
selectedGpuIndex: { def: 0 },
enabledGpuPciIds: { def: [] },
showSystemTray: { def: true },
showClock: { def: true },
showNotificationButton: { def: true },
showBattery: { def: true },
showControlCenterButton: { def: true },
controlCenterShowNetworkIcon: { def: true },
controlCenterShowBluetoothIcon: { def: true },
controlCenterShowAudioIcon: { def: true },
controlCenterWidgets: { def: [
{ id: "volumeSlider", enabled: true, width: 50 },
{ id: "brightnessSlider", enabled: true, width: 50 },
{ id: "wifi", enabled: true, width: 50 },
{ id: "bluetooth", enabled: true, width: 50 },
{ id: "audioOutput", enabled: true, width: 50 },
{ id: "audioInput", enabled: true, width: 50 },
{ id: "nightMode", enabled: true, width: 50 },
{ id: "darkMode", enabled: true, width: 50 }
]},
showWorkspaceIndex: { def: false },
showWorkspacePadding: { def: false },
workspaceScrolling: { def: false },
showWorkspaceApps: { def: false },
maxWorkspaceIcons: { def: 3 },
workspacesPerMonitor: { def: true },
dwlShowAllTags: { def: false },
workspaceNameIcons: { def: {} },
waveProgressEnabled: { def: true },
clockCompactMode: { def: false },
focusedWindowCompactMode: { def: false },
runningAppsCompactMode: { def: true },
keyboardLayoutNameCompactMode: { def: false },
runningAppsCurrentWorkspace: { def: false },
runningAppsGroupByApp: { def: false },
clockDateFormat: { def: "" },
lockDateFormat: { def: "" },
mediaSize: { def: 1 },
dankBarLeftWidgets: { def: ["launcherButton", "workspaceSwitcher", "focusedWindow"], migrate: ["topBarLeftWidgets"] },
dankBarCenterWidgets: { def: ["music", "clock", "weather"], migrate: ["topBarCenterWidgets"] },
dankBarRightWidgets: { def: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"], migrate: ["topBarRightWidgets"] },
dankBarWidgetOrder: { def: [] },
appLauncherViewMode: { def: "list" },
spotlightModalViewMode: { def: "list" },
sortAppsAlphabetically: { def: false },
weatherLocation: { def: "New York, NY" },
weatherCoordinates: { def: "40.7128,-74.0060" },
useAutoLocation: { def: false },
weatherEnabled: { def: true },
networkPreference: { def: "auto" },
vpnLastConnected: { def: "" },
iconTheme: { def: "System Default", onChange: "applyStoredIconTheme" },
availableIconThemes: { def: ["System Default"], persist: false },
systemDefaultIconTheme: { def: "", persist: false },
qt5ctAvailable: { def: false, persist: false },
qt6ctAvailable: { def: false, persist: false },
gtkAvailable: { def: false, persist: false },
launcherLogoMode: { def: "apps" },
launcherLogoCustomPath: { def: "" },
launcherLogoColorOverride: { def: "" },
launcherLogoColorInvertOnMode: { def: false },
launcherLogoBrightness: { def: 0.5 },
launcherLogoContrast: { def: 1 },
launcherLogoSizeOffset: { def: 0 },
fontFamily: { def: "Inter Variable" },
monoFontFamily: { def: "Fira Code" },
fontWeight: { def: 400 },
fontScale: { def: 1.0 },
dankBarFontScale: { def: 1.0 },
notepadUseMonospace: { def: true },
notepadFontFamily: { def: "" },
notepadFontSize: { def: 14 },
notepadShowLineNumbers: { def: false },
notepadTransparencyOverride: { def: -1 },
notepadLastCustomTransparency: { def: 0.7 },
soundsEnabled: { def: true },
useSystemSoundTheme: { def: false },
soundNewNotification: { def: true },
soundVolumeChanged: { def: true },
soundPluggedIn: { def: true },
acMonitorTimeout: { def: 0 },
acLockTimeout: { def: 0 },
acSuspendTimeout: { def: 0 },
acSuspendBehavior: { def: 0 },
batteryMonitorTimeout: { def: 0 },
batteryLockTimeout: { def: 0 },
batterySuspendTimeout: { def: 0 },
batterySuspendBehavior: { def: 0 },
lockBeforeSuspend: { def: false },
loginctlLockIntegration: { def: true },
launchPrefix: { def: "" },
brightnessDevicePins: { def: {} },
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
syncModeWithPortal: { def: true },
showDock: { def: false },
dockAutoHide: { def: false },
dockGroupByApp: { def: false },
dockOpenOnOverview: { def: false },
dockPosition: { def: 1 },
dockSpacing: { def: 4 },
dockBottomGap: { def: 0 },
dockMargin: { def: 0 },
dockIconSize: { def: 40 },
dockIndicatorStyle: { def: "circle" },
notificationOverlayEnabled: { def: false },
dankBarAutoHide: { def: false, migrate: ["topBarAutoHide"] },
dankBarOpenOnOverview: { def: false, migrate: ["topBarOpenOnOverview"] },
dankBarVisible: { def: true, migrate: ["topBarVisible"] },
overviewRows: { def: 2, persist: false },
overviewColumns: { def: 5, persist: false },
overviewScale: { def: 0.16, persist: false },
dankBarSpacing: { def: 4, migrate: ["topBarSpacing"], onChange: "updateNiriLayout" },
dankBarBottomGap: { def: 0, migrate: ["topBarBottomGap"] },
dankBarInnerPadding: { def: 4, migrate: ["topBarInnerPadding"] },
dankBarPosition: { def: 0, migrate: ["dankBarAtBottom", "topBarAtBottom"] },
dankBarIsVertical: { def: false, persist: false },
dankBarSquareCorners: { def: false, migrate: ["topBarSquareCorners"] },
dankBarNoBackground: { def: false, migrate: ["topBarNoBackground"] },
dankBarGothCornersEnabled: { def: false, migrate: ["topBarGothCornersEnabled"] },
dankBarGothCornerRadiusOverride: { def: false },
dankBarGothCornerRadiusValue: { def: 12 },
dankBarBorderEnabled: { def: false },
dankBarBorderColor: { def: "surfaceText" },
dankBarBorderOpacity: { def: 1.0 },
dankBarBorderThickness: { def: 1 },
popupGapsAuto: { def: true },
popupGapsManual: { def: 4 },
modalDarkenBackground: { def: true },
lockScreenShowPowerActions: { def: true },
enableFprint: { def: false },
maxFprintTries: { def: 3 },
fprintdAvailable: { def: false, persist: false },
hideBrightnessSlider: { def: false },
notificationTimeoutLow: { def: 5000 },
notificationTimeoutNormal: { def: 5000 },
notificationTimeoutCritical: { def: 0 },
notificationPopupPosition: { def: 0 },
osdAlwaysShowValue: { def: false },
powerActionConfirm: { def: true },
customPowerActionLock: { def: "" },
customPowerActionLogout: { def: "" },
customPowerActionSuspend: { def: "" },
customPowerActionHibernate: { def: "" },
customPowerActionReboot: { def: "" },
customPowerActionPowerOff: { def: "" },
updaterUseCustomCommand: { def: false },
updaterCustomCommand: { def: "" },
updaterTerminalAdditionalParams: { def: "" },
screenPreferences: { def: {} },
showOnLastDisplay: { def: {} }
};
function getValidKeys() {
return Object.keys(SPEC).filter(function(k) { return SPEC[k].persist !== false; }).concat(["configVersion"]);
}
function set(root, key, value, saveFn, hooks) {
if (!(key in SPEC)) return;
root[key] = value;
var hookName = SPEC[key].onChange;
if (hookName && hooks && hooks[hookName]) {
hooks[hookName](root);
}
saveFn();
}

View File

@@ -0,0 +1,118 @@
.pragma library
.import "./SettingsSpec.js" as SpecModule
function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC;
for (var k in SPEC) {
var spec = SPEC[k];
root[k] = spec.def;
}
if (!jsonObj) return;
for (var k in jsonObj) {
if (!SPEC[k]) continue;
var raw = jsonObj[k];
var spec = SPEC[k];
var coerce = spec.coerce;
root[k] = coerce ? (coerce(raw) !== undefined ? coerce(raw) : root[k]) : raw;
}
}
function toJson(root) {
var SPEC = SpecModule.SPEC;
var out = {};
for (var k in SPEC) {
if (SPEC[k].persist === false) continue;
out[k] = root[k];
}
out.configVersion = root.settingsConfigVersion;
return out;
}
function migrate(root, jsonObj) {
var SPEC = SpecModule.SPEC;
if (!jsonObj) return;
if (jsonObj.themeIndex !== undefined || jsonObj.themeIsDynamic !== undefined) {
var themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"];
if (jsonObj.themeIsDynamic) {
root.currentThemeName = "dynamic";
} else if (jsonObj.themeIndex >= 0 && jsonObj.themeIndex < themeNames.length) {
root.currentThemeName = themeNames[jsonObj.themeIndex];
}
console.info("Auto-migrated theme from index", jsonObj.themeIndex, "to", root.currentThemeName);
}
if ((jsonObj.dankBarWidgetOrder && jsonObj.dankBarWidgetOrder.length > 0) ||
(jsonObj.topBarWidgetOrder && jsonObj.topBarWidgetOrder.length > 0)) {
if (jsonObj.dankBarLeftWidgets === undefined && jsonObj.dankBarCenterWidgets === undefined && jsonObj.dankBarRightWidgets === undefined) {
var widgetOrder = jsonObj.dankBarWidgetOrder || jsonObj.topBarWidgetOrder;
root.dankBarLeftWidgets = widgetOrder.filter(function(w) { return ["launcherButton", "workspaceSwitcher", "focusedWindow"].indexOf(w) >= 0; });
root.dankBarCenterWidgets = widgetOrder.filter(function(w) { return ["clock", "music", "weather"].indexOf(w) >= 0; });
root.dankBarRightWidgets = widgetOrder.filter(function(w) { return ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"].indexOf(w) >= 0; });
}
}
if (jsonObj.useOSLogo !== undefined) {
root.launcherLogoMode = jsonObj.useOSLogo ? "os" : "apps";
root.launcherLogoColorOverride = jsonObj.osLogoColorOverride !== undefined ? jsonObj.osLogoColorOverride : "";
root.launcherLogoBrightness = jsonObj.osLogoBrightness !== undefined ? jsonObj.osLogoBrightness : 0.5;
root.launcherLogoContrast = jsonObj.osLogoContrast !== undefined ? jsonObj.osLogoContrast : 1;
}
if (jsonObj.mediaCompactMode !== undefined && jsonObj.mediaSize === undefined) {
root.mediaSize = jsonObj.mediaCompactMode ? 0 : 1;
}
for (var k in SPEC) {
var spec = SPEC[k];
if (!spec.migrate) continue;
for (var i = 0; i < spec.migrate.length; i++) {
var oldKey = spec.migrate[i];
if (jsonObj[oldKey] !== undefined && jsonObj[k] === undefined) {
var raw = jsonObj[oldKey];
var coerce = spec.coerce;
root[k] = coerce ? (coerce(raw) !== undefined ? coerce(raw) : root[k]) : raw;
break;
}
}
}
if (jsonObj.dankBarAtBottom !== undefined || jsonObj.topBarAtBottom !== undefined) {
var atBottom = jsonObj.dankBarAtBottom !== undefined ? jsonObj.dankBarAtBottom : jsonObj.topBarAtBottom;
root.dankBarPosition = atBottom ? 1 : 0;
}
if (jsonObj.pluginSettings !== undefined) {
root.pluginSettings = jsonObj.pluginSettings;
return true;
}
return false;
}
function cleanup(fileText) {
var getValidKeys = SpecModule.getValidKeys;
if (!fileText || !fileText.trim()) return;
try {
var settings = JSON.parse(fileText);
var validKeys = getValidKeys();
var needsSave = false;
for (var key in settings) {
if (validKeys.indexOf(key) < 0) {
console.log("SettingsData: Removing unused key:", key);
delete settings[key];
needsSave = true;
}
}
return needsSave ? JSON.stringify(settings, null, 2) : null;
} catch (e) {
console.warn("SettingsData: Failed to cleanup unused keys:", e.message);
return null;
}
}

View File

@@ -46,12 +46,20 @@ Item {
item.popoutService = PopoutService
}
item.pluginId = pluginId
console.log("Daemon plugin loaded:", pluginId)
console.info("Daemon plugin loaded:", pluginId)
}
}
}
}
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
Lock {
@@ -205,11 +213,31 @@ Item {
}
}
PolkitAuthModal {
id: polkitAuthModal
}
property string lastCredentialsToken: ""
property var lastCredentialsTime: 0
Connections {
target: NetworkService
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason)
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
const now = Date.now()
const timeSinceLastPrompt = now - lastCredentialsTime
if (wifiPasswordModal.shouldBeVisible && timeSinceLastPrompt < 1000) {
NetworkService.cancelCredentials(lastCredentialsToken)
lastCredentialsToken = token
lastCredentialsTime = now
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
return
}
lastCredentialsToken = token
lastCredentialsTime = now
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
}
}

View File

@@ -135,24 +135,22 @@ Item {
}
function toggle(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
if (root.dankDashPopoutLoader.item.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false
} else {
if (root.dankBarLoader.item && root.dankBarLoader.item.triggerWallpaperBrowserOnFocusedScreen()) {
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "wallpaper":
root.dankDashPopoutLoader.item.currentTabIndex = 2
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}

View File

@@ -0,0 +1,364 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:bluetooth-pairing"
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.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
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

@@ -26,7 +26,7 @@ Rectangle {
if (isSelected) {
return Theme.primaryPressed
}
return mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
return mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
}
Row {

View File

@@ -12,6 +12,8 @@ import qs.Widgets
DankModal {
id: clipboardHistoryModal
layerNamespace: "dms:clipboard"
property int totalCount: 0
property var clipboardEntries: []
property string searchText: ""
@@ -136,7 +138,7 @@ DankModal {
visible: false
width: ClipboardConstants.modalWidth
height: ClipboardConstants.modalHeight
backgroundColor: Theme.popupBackground()
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium
borderWidth: 1

View File

@@ -58,7 +58,7 @@ DankModal {
shouldBeVisible: false
allowStacking: true
width: 350
height: 160
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 160
enableShadow: true
shouldHaveFocus: true
onBackgroundClicked: {
@@ -158,11 +158,17 @@ DankModal {
content: Component {
Item {
anchors.fill: parent
implicitHeight: mainColumn.implicitHeight
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
spacing: Theme.spacingM
id: mainColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
anchors.topMargin: Theme.spacingL
spacing: 0
StyledText {
text: confirmTitle
@@ -173,6 +179,11 @@ DankModal {
horizontalAlignment: Text.AlignHCenter
}
Item {
width: 1
height: Theme.spacingL
}
StyledText {
text: confirmMessage
font.pixelSize: Theme.fontSizeMedium
@@ -183,7 +194,8 @@ DankModal {
}
Item {
height: Theme.spacingS
width: 1
height: Theme.spacingL * 1.5
}
Row {
@@ -265,6 +277,11 @@ DankModal {
}
}
}
Item {
width: 1
height: Theme.spacingL
}
}
}
}

View File

@@ -8,7 +8,8 @@ import qs.Services
PanelWindow {
id: root
WlrLayershell.namespace: "quickshell:modal"
property string layerNamespace: "dms:modal"
WlrLayershell.namespace: layerNamespace
property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader
@@ -17,17 +18,7 @@ PanelWindow {
property real height: 300
readonly property real screenWidth: screen ? screen.width : 1920
readonly property real screenHeight: screen ? screen.height : 1080
readonly property real dpr: {
if (CompositorService.isNiri && screen) {
const niriScale = NiriService.displayScales[screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return (screen?.devicePixelRatio) || 1
}
readonly property real dpr: CompositorService.getScreenScale(screen)
property bool showBackground: true
property real backgroundOpacity: 0.5
property string positioning: "center"
@@ -120,24 +111,24 @@ PanelWindow {
bottom: true
}
MouseArea {
anchors.fill: parent
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
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) {
root.backgroundClicked()
}
}
}
Rectangle {
id: background
anchors.fill: parent
color: "black"
opacity: root.showBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.showBackground
MouseArea {
anchors.fill: parent
enabled: root.closeOnBackgroundClick
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) {
root.backgroundClicked()
}
}
}
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.showBackground && SettingsData.modalDarkenBackground
Behavior on opacity {
NumberAnimation {
@@ -148,12 +139,11 @@ PanelWindow {
}
}
Rectangle {
id: contentContainer
Item {
id: modalContainer
width: Theme.px(root.width, dpr)
height: Theme.px(root.height, dpr)
anchors.centerIn: undefined
x: {
if (positioning === "center") {
return Theme.snap((root.screenWidth - width) / 2, dpr)
@@ -174,99 +164,93 @@ PanelWindow {
}
return 0
}
color: root.backgroundColor
radius: root.cornerRadius
border.color: root.borderColor
border.width: root.borderWidth
clip: false
layer.enabled: true
layer.smooth: true
opacity: root.shouldBeVisible ? 1 : 0
transform: [scaleTransform, motionTransform]
Scale {
id: scaleTransform
readonly property bool slide: root.animationType === "slide"
readonly property real offsetX: slide ? 15 : 0
readonly property real offsetY: slide ? -30 : root.animationOffset
origin.x: contentContainer.width / 2
origin.y: contentContainer.height / 2
xScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
yScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
property real animX: 0
property real animY: 0
property real scaleValue: root.animationScaleCollapsed
Behavior on xScale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
Behavior on yScale {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
Connections {
target: root
function onShouldBeVisibleChanged() {
modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr)
modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr)
modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed
}
}
Translate {
id: motionTransform
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(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 {
Behavior on animX {
NumberAnimation {
duration: animationDuration
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible
Behavior on animY {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on scaleValue {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Rectangle {
id: contentContainer
anchors.centerIn: parent
width: parent.width
height: parent.height
color: root.backgroundColor
radius: root.cornerRadius
border.color: root.borderColor
border.width: root.borderWidth
clip: false
layer.enabled: true
layer.smooth: false
layer.textureSize: Qt.size(width * root.dpr, height * root.dpr)
layer.textureMirroring: ShaderEffectSource.NoMirroring
opacity: root.shouldBeVisible ? 1 : 0
scale: modalContainer.scaleValue
x: Theme.snap(modalContainer.animX + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5, root.dpr)
y: Theme.snap(modalContainer.animY + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5, root.dpr)
Item {
id: directContentWrapper
Behavior on opacity {
NumberAnimation {
duration: animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
FocusScope {
anchors.fill: parent
visible: root.directContent !== null
focus: true
focus: root.shouldBeVisible
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
Qt.callLater(() => root.directContent.forceActiveFocus())
}
}
Item {
id: directContentWrapper
Connections {
function onDirectContentChanged() {
anchors.fill: parent
visible: root.directContent !== null
focus: true
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
@@ -274,23 +258,33 @@ PanelWindow {
}
}
target: root
Connections {
function onDirectContentChanged() {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
Qt.callLater(() => root.directContent.forceActiveFocus())
}
}
target: root
}
}
}
Loader {
id: contentLoader
Loader {
id: contentLoader
anchors.fill: parent
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || root.visible)
asynchronous: false
focus: true
clip: false
visible: root.directContent === null
anchors.fill: parent
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || root.visible)
asynchronous: false
focus: true
clip: false
visible: root.directContent === null
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus())
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus())
}
}
}
}

View File

@@ -10,6 +10,8 @@ import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:color-picker"
property string pickerTitle: "Choose Color"
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
property var onColorSelectedCallback: null
@@ -45,6 +47,12 @@ DankModal {
close()
}
function hideInstant() {
onColorSelectedCallback = null
shouldBeVisible = false
visible = false
}
onColorSelected: (color) => {
if (onColorSelectedCallback) {
onColorSelectedCallback(color)
@@ -78,7 +86,7 @@ DankModal {
}
function pickColorFromScreen() {
hide()
hideInstant()
Proc.runCommand("hyprpicker", ["hyprpicker", "--format=hex"], (output, errorCode) => {
if (errorCode !== 0) {
console.warn("hyprpicker exited with code:", errorCode)
@@ -376,7 +384,7 @@ DankModal {
if (index < SessionData.recentColors.length) {
return SessionData.recentColors[index]
}
return Theme.surfaceContainerHigh
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
}
opacity: index < SessionData.recentColors.length ? 1.0 : 0.3

View File

@@ -0,0 +1,204 @@
import QtQuick
import QtQuick.Effects
import qs.Common
import qs.Widgets
StyledRect {
id: delegateRoot
required property bool fileIsDir
required property string filePath
required property string fileName
required property int index
property bool weMode: false
property var iconSizes: [80, 120, 160, 200]
property int iconSizeIndex: 1
property int selectedIndex: -1
property bool keyboardNavigationActive: false
signal itemClicked(int index, string path, string name, bool isDir)
signal itemSelected(int index, string path, string name, bool isDir)
function getFileExtension(fileName) {
const parts = fileName.split('.')
if (parts.length > 1) {
return parts[parts.length - 1].toLowerCase()
}
return ""
}
function determineFileType(fileName) {
const ext = getFileExtension(fileName)
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
if (imageExts.includes(ext)) {
return "image"
}
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
if (videoExts.includes(ext)) {
return "video"
}
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
if (audioExts.includes(ext)) {
return "audio"
}
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
if (codeExts.includes(ext)) {
return "code"
}
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
if (docExts.includes(ext)) {
return "document"
}
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
if (archiveExts.includes(ext)) {
return "archive"
}
if (!ext || fileName.indexOf('.') === -1) {
return "binary"
}
return "file"
}
function isImageFile(fileName) {
if (!fileName) {
return false
}
return determineFileType(fileName) === "image"
}
function getIconForFile(fileName) {
const lowerName = fileName.toLowerCase()
if (lowerName.startsWith("dockerfile")) {
return "docker"
}
const ext = fileName.split('.').pop()
return ext || ""
}
width: weMode ? 245 : iconSizes[iconSizeIndex] + 16
height: weMode ? 205 : iconSizes[iconSizeIndex] + 48
radius: Theme.cornerRadius
color: {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
return Theme.surfacePressed
return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
}
border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : "transparent"
border.width: (keyboardNavigationActive && delegateRoot.index === selectedIndex) ? 2 : 0
Component.onCompleted: {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
}
onSelectedIndexChanged: {
if (keyboardNavigationActive && selectedIndex === delegateRoot.index)
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
Item {
width: weMode ? 225 : (iconSizes[iconSizeIndex] - 8)
height: weMode ? 165 : (iconSizes[iconSizeIndex] - 8)
anchors.horizontalCenter: parent.horizontalCenter
CachingImage {
id: gridPreviewImage
anchors.fill: parent
anchors.margins: 2
property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga"]
property int weExtIndex: 0
source: {
if (weMode && delegateRoot.fileIsDir) {
return "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
}
return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
}
onStatusChanged: {
if (weMode && delegateRoot.fileIsDir && status === Image.Error) {
if (weExtIndex < weExtensions.length - 1) {
weExtIndex++
source = "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
} else {
source = ""
}
}
}
fillMode: Image.PreserveAspectCrop
maxCacheSize: weMode ? 225 : iconSizes[iconSizeIndex]
visible: false
}
MultiEffect {
anchors.fill: parent
anchors.margins: 2
source: gridPreviewImage
maskEnabled: true
maskSource: gridImageMask
visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) || (weMode && delegateRoot.fileIsDir))
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: gridImageMask
anchors.fill: parent
anchors.margins: 2
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: "black"
antialiasing: true
}
}
DankNFIcon {
anchors.centerIn: parent
name: delegateRoot.fileIsDir ? "folder" : getIconForFile(delegateRoot.fileName)
size: iconSizes[iconSizeIndex] * 0.45
color: delegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
visible: (!delegateRoot.fileIsDir && !isImageFile(delegateRoot.fileName)) || (delegateRoot.fileIsDir && !weMode)
}
}
StyledText {
text: delegateRoot.fileName || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
width: delegateRoot.width - Theme.spacingM
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
maximumLineCount: 2
wrapMode: Text.Wrap
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
itemClicked(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
}
}
}

View File

@@ -0,0 +1,209 @@
import QtQuick
import QtQuick.Effects
import qs.Common
import qs.Widgets
StyledRect {
id: listDelegateRoot
required property bool fileIsDir
required property string filePath
required property string fileName
required property int index
required property var fileModified
required property int fileSize
property int selectedIndex: -1
property bool keyboardNavigationActive: false
signal itemClicked(int index, string path, string name, bool isDir)
signal itemSelected(int index, string path, string name, bool isDir)
function getFileExtension(fileName) {
const parts = fileName.split('.')
if (parts.length > 1) {
return parts[parts.length - 1].toLowerCase()
}
return ""
}
function determineFileType(fileName) {
const ext = getFileExtension(fileName)
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
if (imageExts.includes(ext)) {
return "image"
}
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
if (videoExts.includes(ext)) {
return "video"
}
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
if (audioExts.includes(ext)) {
return "audio"
}
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
if (codeExts.includes(ext)) {
return "code"
}
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
if (docExts.includes(ext)) {
return "document"
}
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
if (archiveExts.includes(ext)) {
return "archive"
}
if (!ext || fileName.indexOf('.') === -1) {
return "binary"
}
return "file"
}
function isImageFile(fileName) {
if (!fileName) {
return false
}
return determineFileType(fileName) === "image"
}
function getIconForFile(fileName) {
const lowerName = fileName.toLowerCase()
if (lowerName.startsWith("dockerfile")) {
return "docker"
}
const ext = fileName.split('.').pop()
return ext || ""
}
function formatFileSize(size) {
if (size < 1024)
return size + " B"
if (size < 1024 * 1024)
return (size / 1024).toFixed(1) + " KB"
if (size < 1024 * 1024 * 1024)
return (size / (1024 * 1024)).toFixed(1) + " MB"
return (size / (1024 * 1024 * 1024)).toFixed(1) + " GB"
}
height: 44
radius: Theme.cornerRadius
color: {
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
return Theme.surfacePressed
return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
}
border.color: keyboardNavigationActive && listDelegateRoot.index === selectedIndex ? Theme.primary : "transparent"
border.width: (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) ? 2 : 0
Component.onCompleted: {
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
}
onSelectedIndexChanged: {
if (keyboardNavigationActive && selectedIndex === listDelegateRoot.index)
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
}
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingS
Item {
width: 28
height: 28
anchors.verticalCenter: parent.verticalCenter
CachingImage {
id: listPreviewImage
anchors.fill: parent
source: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? ("file://" + listDelegateRoot.filePath) : ""
fillMode: Image.PreserveAspectCrop
maxCacheSize: 32
visible: false
}
MultiEffect {
anchors.fill: parent
source: listPreviewImage
maskEnabled: true
maskSource: listImageMask
visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: listImageMask
anchors.fill: parent
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: "black"
antialiasing: true
}
}
DankNFIcon {
anchors.centerIn: parent
name: listDelegateRoot.fileIsDir ? "folder" : getIconForFile(listDelegateRoot.fileName)
size: Theme.iconSize - 2
color: listDelegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
visible: listDelegateRoot.fileIsDir || !isImageFile(listDelegateRoot.fileName)
}
}
StyledText {
text: listDelegateRoot.fileName || ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width - 280
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
clip: true
}
StyledText {
text: listDelegateRoot.fileIsDir ? "" : formatFileSize(listDelegateRoot.fileSize)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: 70
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: Qt.formatDateTime(listDelegateRoot.fileModified, "MMM d, yyyy h:mm AP")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: 140
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: listMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
itemClicked(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
import QtQuick
import qs.Common
import qs.Widgets
Row {
id: navigation
property string currentPath: ""
property string homeDir: ""
property bool backButtonFocused: false
property bool keyboardNavigationActive: false
property bool showSidebar: true
property bool pathEditMode: false
property bool pathInputHasFocus: false
signal navigateUp()
signal navigateTo(string path)
signal pathInputFocusChanged(bool hasFocus)
height: 40
leftPadding: Theme.spacingM
rightPadding: Theme.spacingM
spacing: Theme.spacingS
StyledRect {
width: 32
height: 32
radius: Theme.cornerRadius
color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive)) && currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
opacity: currentPath !== homeDir ? 1 : 0
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "arrow_back"
size: Theme.iconSizeSmall
color: Theme.surfaceText
}
MouseArea {
id: backButtonMouseArea
anchors.fill: parent
hoverEnabled: currentPath !== homeDir
cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: currentPath !== homeDir
onClicked: navigation.navigateUp()
}
}
Item {
width: Math.max(0, (parent?.width ?? 0) - 40 - Theme.spacingS - (showSidebar ? 0 : 80))
height: 32
anchors.verticalCenter: parent.verticalCenter
StyledRect {
anchors.fill: parent
radius: Theme.cornerRadius
color: pathEditMode ? Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) : "transparent"
border.color: pathEditMode ? Theme.primary : "transparent"
border.width: pathEditMode ? 1 : 0
visible: !pathEditMode
StyledText {
id: pathDisplay
text: currentPath.replace("file://", "")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
elide: Text.ElideMiddle
verticalAlignment: Text.AlignVCenter
maximumLineCount: 1
wrapMode: Text.NoWrap
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: {
pathEditMode = true
pathInput.text = currentPath.replace("file://", "")
Qt.callLater(() => pathInput.forceActiveFocus())
}
}
}
DankTextField {
id: pathInput
anchors.fill: parent
visible: pathEditMode
topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS
onAccepted: {
const newPath = text.trim()
if (newPath !== "") {
navigation.navigateTo(newPath)
}
pathEditMode = false
}
Keys.onEscapePressed: {
pathEditMode = false
}
Keys.onDownPressed: {
pathEditMode = false
}
onActiveFocusChanged: {
navigation.pathInputFocusChanged(activeFocus)
if (!activeFocus && pathEditMode) {
pathEditMode = false
}
}
}
}
Row {
spacing: Theme.spacingXS
visible: !showSidebar
anchors.verticalCenter: parent.verticalCenter
DankActionButton {
circular: false
iconName: "sort"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText
}
}
}

View File

@@ -0,0 +1,127 @@
import QtQuick
import qs.Common
import qs.Widgets
Item {
id: overwriteDialog
property bool showDialog: false
property string pendingFilePath: ""
signal confirmed(string filePath)
signal cancelled()
visible: showDialog
focus: showDialog
Keys.onEscapePressed: {
cancelled()
}
Keys.onReturnPressed: {
confirmed(pendingFilePath)
}
Rectangle {
anchors.fill: parent
color: Theme.shadowStrong
opacity: 0.8
MouseArea {
anchors.fill: parent
onClicked: {
cancelled()
}
}
}
StyledRect {
anchors.centerIn: parent
width: 400
height: 160
color: Theme.surfaceContainer
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
font.weight: Font.Medium
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
color: Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
StyledRect {
width: 80
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceVariantHover : Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
StyledText {
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: {
cancelled()
}
}
}
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")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: overwriteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
confirmed(pendingFilePath)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,74 @@
import QtQuick
import qs.Common
import qs.Widgets
Row {
id: saveRow
property bool saveMode: false
property string defaultFileName: ""
property string currentPath: ""
signal saveRequested(string filePath)
height: saveMode ? 40 : 0
visible: saveMode
spacing: Theme.spacingM
DankTextField {
id: fileNameInput
width: parent.width - saveButton.width - Theme.spacingM
height: 40
text: defaultFileName
placeholderText: I18n.tr("Enter filename...")
ignoreLeftRightKeys: false
focus: saveMode
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
Component.onCompleted: {
if (saveMode)
Qt.callLater(() => {
forceActiveFocus()
})
}
onAccepted: {
if (text.trim() !== "") {
var basePath = currentPath.replace(/^file:\/\//, '')
var fullPath = basePath + "/" + text.trim()
fullPath = fullPath.replace(/\/+/g, '/')
saveRequested(fullPath)
}
}
}
StyledRect {
id: saveButton
width: 80
height: 40
color: fileNameInput.text.trim() !== "" ? Theme.primary : Theme.surfaceVariant
radius: Theme.cornerRadius
StyledText {
anchors.centerIn: parent
text: I18n.tr("Save")
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
}
StateLayer {
stateColor: Theme.primary
cornerRadius: Theme.cornerRadius
enabled: fileNameInput.text.trim() !== ""
onClicked: {
if (fileNameInput.text.trim() !== "") {
var basePath = currentPath.replace(/^file:\/\//, '')
var fullPath = basePath + "/" + fileNameInput.text.trim()
fullPath = fullPath.replace(/\/+/g, '/')
saveRequested(fullPath)
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: sidebar
property var quickAccessLocations: []
property string currentPath: ""
signal locationSelected(string path)
width: 200
color: Theme.surface
clip: true
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 4
StyledText {
text: "Quick Access"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
leftPadding: Theme.spacingS
bottomPadding: Theme.spacingXS
}
Repeater {
model: quickAccessLocations
StyledRect {
width: parent?.width ?? 0
height: 38
radius: Theme.cornerRadius
color: quickAccessMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : (currentPath === modelData?.path ? Theme.surfacePressed : "transparent")
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: modelData?.icon ?? ""
size: Theme.iconSize - 2
color: currentPath === modelData?.path ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData?.name ?? ""
font.pixelSize: Theme.fontSizeMedium
color: currentPath === modelData?.path ? Theme.primary : Theme.surfaceText
font.weight: currentPath === modelData?.path ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: quickAccessMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: locationSelected(modelData?.path ?? "")
}
}
}
}
}

View File

@@ -0,0 +1,183 @@
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: sortMenu
property string sortBy: "name"
property bool sortAscending: true
signal sortBySelected(string value)
signal sortOrderSelected(bool ascending)
width: 200
height: sortColumn.height + Theme.spacingM * 2
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.color: Theme.outlineMedium
border.width: 1
visible: false
z: 100
Column {
id: sortColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
StyledText {
text: "Sort By"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
}
Repeater {
model: [{
"name": "Name",
"value": "name"
}, {
"name": "Size",
"value": "size"
}, {
"name": "Modified",
"value": "modified"
}, {
"name": "Type",
"value": "type"
}]
StyledRect {
width: sortColumn?.width ?? 0
height: 32
radius: Theme.cornerRadius
color: sortMouseArea.containsMouse ? Theme.surfaceVariant : (sortBy === modelData?.value ? Theme.surfacePressed : "transparent")
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: sortBy === modelData?.value ? "check" : ""
size: Theme.iconSizeSmall
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: sortBy === modelData?.value
}
StyledText {
text: modelData?.name ?? ""
font.pixelSize: Theme.fontSizeMedium
color: sortBy === modelData?.value ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: sortMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
sortMenu.sortBySelected(modelData?.value ?? "name")
sortMenu.visible = false
}
}
}
}
StyledRect {
width: sortColumn.width
height: 1
color: Theme.outline
}
StyledText {
text: "Order"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
topPadding: Theme.spacingXS
}
StyledRect {
width: sortColumn?.width ?? 0
height: 32
radius: Theme.cornerRadius
color: ascMouseArea.containsMouse ? Theme.surfaceVariant : (sortAscending ? Theme.surfacePressed : "transparent")
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: "arrow_upward"
size: Theme.iconSizeSmall
color: sortAscending ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Ascending"
font.pixelSize: Theme.fontSizeMedium
color: sortAscending ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: ascMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
sortMenu.sortOrderSelected(true)
sortMenu.visible = false
}
}
}
StyledRect {
width: sortColumn?.width ?? 0
height: 32
radius: Theme.cornerRadius
color: descMouseArea.containsMouse ? Theme.surfaceVariant : (!sortAscending ? Theme.surfacePressed : "transparent")
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: "arrow_downward"
size: Theme.iconSizeSmall
color: !sortAscending ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Descending"
font.pixelSize: Theme.fontSizeMedium
color: !sortAscending ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: descMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
sortMenu.sortOrderSelected(false)
sortMenu.visible = false
}
}
}
}
}

View File

@@ -9,10 +9,16 @@ import qs.Widgets
DankModal {
id: root
width: 1400
height: 900
layerNamespace: "dms:hyprkeybinds"
property real _maxW: Math.min(Screen.width * 0.92, 1200)
property real _maxH: Math.min(Screen.height * 0.92, 900)
width: _maxW
height: _maxH
onBackgroundClicked: close()
Shortcut { sequence: "Esc"; onActivated: root.close() }
function categorizeKeybinds() {
const categories = {
"Workspace": [],

View File

@@ -8,6 +8,8 @@ import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:network-info"
property bool networkInfoModalVisible: false
property string networkSSID: ""
property var networkData: null

View File

@@ -8,6 +8,8 @@ import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:network-info-wired"
property bool networkWiredInfoModalVisible: false
property string networkID: ""
property var networkData: null

View File

@@ -9,6 +9,8 @@ import qs.Widgets
DankModal {
id: notificationModal
layerNamespace: "dms:notification-modal"
property bool notificationModalOpen: false
property var notificationListRef: null

358
Modals/PolkitAuthModal.qml Normal file
View File

@@ -0,0 +1,358 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:polkit"
property string passwordInput: ""
property var currentFlow: PolkitService.agent?.flow
property bool isLoading: false
property real minHeight: 240
function show() {
passwordInput = ""
isLoading = false
open()
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus()
}
})
}
shouldBeVisible: false
width: 420
height: Math.max(minHeight, contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240)
Connections {
target: contentLoader.item
function onImplicitHeightChanged() {
if (shouldBeVisible && contentLoader.item) {
const newHeight = contentLoader.item.implicitHeight + Theme.spacingM * 2
if (newHeight > minHeight) {
minHeight = newHeight
}
}
}
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus()
}
})
}
onClosed: {
passwordInput = ""
isLoading = false
}
onBackgroundClicked: () => {
if (currentFlow && !isLoading) {
currentFlow.cancelAuthenticationRequest()
}
}
Connections {
target: PolkitService.agent
enabled: PolkitService.polkitAvailable
function onAuthenticationRequestStarted() {
show()
}
function onIsActiveChanged() {
if (!(PolkitService.agent?.isActive ?? false)) {
close()
}
}
}
Connections {
target: currentFlow
enabled: currentFlow !== null
function onIsResponseRequiredChanged() {
if (currentFlow.isResponseRequired) {
isLoading = false
passwordInput = ""
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus()
}
}
}
function onAuthenticationSucceeded() {
close()
}
function onAuthenticationFailed() {
isLoading = false
}
function onAuthenticationRequestCancelled() {
close()
}
}
content: Component {
FocusScope {
id: authContent
property alias passwordField: passwordField
anchors.fill: parent
focus: true
implicitHeight: headerRow.implicitHeight + mainColumn.implicitHeight + Theme.spacingM
Keys.onEscapePressed: event => {
if (currentFlow && !isLoading) {
currentFlow.cancelAuthenticationRequest()
}
event.accepted = true
}
Row {
id: headerRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Authentication Required")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: currentFlow?.message ?? ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.Wrap
}
StyledText {
visible: (currentFlow?.supplementaryMessage ?? "") !== ""
text: currentFlow?.supplementaryMessage ?? ""
font.pixelSize: Theme.fontSizeSmall
color: (currentFlow?.supplementaryIsError ?? false) ? Theme.error : Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.Wrap
opacity: (currentFlow?.supplementaryIsError ?? false) ? 1 : 0.8
}
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
enabled: !isLoading
opacity: enabled ? 1 : 0.5
onClicked: () => {
if (currentFlow) {
currentFlow.cancelAuthenticationRequest()
}
}
}
}
Column {
id: mainColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.bottomMargin: Theme.spacingM
spacing: Theme.spacingM
StyledText {
text: currentFlow?.inputPrompt ?? ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
visible: (currentFlow?.inputPrompt ?? "") !== ""
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: passwordField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: passwordField.activeFocus ? 2 : 1
opacity: isLoading ? 0.5 : 1
MouseArea {
anchors.fill: parent
enabled: !isLoading
onClicked: () => {
passwordField.forceActiveFocus()
}
}
DankTextField {
id: passwordField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: passwordInput
echoMode: (currentFlow?.responseVisible ?? false) ? TextInput.Normal : TextInput.Password
placeholderText: ""
backgroundColor: "transparent"
enabled: !isLoading
onTextEdited: () => {
passwordInput = text
}
onAccepted: () => {
if (passwordInput.length > 0 && currentFlow && !isLoading) {
isLoading = true
currentFlow.submit(passwordInput)
passwordInput = ""
}
}
}
}
Item {
width: parent.width
height: (currentFlow?.failed ?? false) ? failedText.implicitHeight : 0
visible: height > 0
StyledText {
id: failedText
text: I18n.tr("Authentication failed, please try again")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width
opacity: (currentFlow?.failed ?? false) ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Item {
width: parent.width
height: 40
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
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
enabled: !isLoading
opacity: enabled ? 1 : 0.5
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
enabled: parent.enabled
onClicked: () => {
if (currentFlow) {
currentFlow.cancelAuthenticationRequest()
}
}
}
}
Rectangle {
width: Math.max(80, authText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: authArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: !isLoading && (passwordInput.length > 0 || !(currentFlow?.isResponseRequired ?? true))
opacity: enabled ? 1 : 0.5
StyledText {
id: authText
anchors.centerIn: parent
text: I18n.tr("Authenticate")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: authArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
if (currentFlow && !isLoading) {
isLoading = true
currentFlow.submit(passwordInput)
passwordInput = ""
}
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}
}

View File

@@ -7,6 +7,8 @@ import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:power-menu"
property int selectedIndex: 0
property int optionCount: SessionService.hibernateSupported ? 5 : 4
property rect parentBounds: Qt.rect(0, 0, 0, 0)

View File

@@ -9,6 +9,8 @@ import qs.Widgets
DankModal {
id: processListModal
layerNamespace: "dms:process-list-modal"
property int currentTab: 0
property var tabNames: ["Processes", "Performance", "System"]
@@ -44,7 +46,7 @@ DankModal {
width: 900
height: 680
visible: false
backgroundColor: Theme.popupBackground()
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
cornerRadius: Theme.cornerRadius
enableShadow: true
onBackgroundClicked: () => {
@@ -181,7 +183,7 @@ DankModal {
Rectangle {
Layout.fillWidth: true
height: 52
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Theme.outlineLight
border.width: 1
@@ -281,7 +283,7 @@ DankModal {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineLight
border.width: 1

View File

@@ -64,7 +64,7 @@ Item {
text: I18n.tr("Show Power Actions")
description: I18n.tr("Show power, restart, and logout buttons on the lock screen")
checked: SettingsData.lockScreenShowPowerActions
onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked)
onToggled: checked => SettingsData.set("lockScreenShowPowerActions", checked)
}
StyledText {
@@ -84,7 +84,7 @@ Item {
enabled: SessionService.loginctlAvailable
onToggled: checked => {
if (SessionService.loginctlAvailable) {
SettingsData.setLoginctlLockIntegration(checked)
SettingsData.set("loginctlLockIntegration", checked)
}
}
}
@@ -95,7 +95,7 @@ Item {
description: I18n.tr("Automatically lock the screen when the system prepares to suspend")
checked: SettingsData.lockBeforeSuspend
visible: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
onToggled: checked => SettingsData.setLockBeforeSuspend(checked)
onToggled: checked => SettingsData.set("lockBeforeSuspend", checked)
}
DankToggle {
@@ -104,7 +104,7 @@ Item {
description: I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)")
checked: SettingsData.enableFprint
visible: SettingsData.fprintdAvailable
onToggled: checked => SettingsData.setEnableFprint(checked)
onToggled: checked => SettingsData.set("enableFprint", checked)
}
}
}
@@ -186,9 +186,9 @@ Item {
if (index >= 0) {
const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) {
SettingsData.setAcLockTimeout(timeout)
SettingsData.set("acLockTimeout", timeout)
} else {
SettingsData.setBatteryLockTimeout(timeout)
SettingsData.set("batteryLockTimeout", timeout)
}
}
}
@@ -222,9 +222,9 @@ Item {
if (index >= 0) {
const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) {
SettingsData.setAcMonitorTimeout(timeout)
SettingsData.set("acMonitorTimeout", timeout)
} else {
SettingsData.setBatteryMonitorTimeout(timeout)
SettingsData.set("batteryMonitorTimeout", timeout)
}
}
}
@@ -258,46 +258,52 @@ Item {
if (index >= 0) {
const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) {
SettingsData.setAcSuspendTimeout(timeout)
SettingsData.set("acSuspendTimeout", timeout)
} else {
SettingsData.setBatterySuspendTimeout(timeout)
SettingsData.set("batterySuspendTimeout", timeout)
}
}
}
}
DankDropdown {
id: hibernateDropdown
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Hibernate system after")
options: timeoutOptions
Column {
width: parent.width
spacing: Theme.spacingS
visible: SessionService.hibernateSupported
Connections {
target: powerCategory
function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout
const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout)
hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never"
StyledText {
text: I18n.tr("Suspend behavior")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
}
DankButtonGroup {
id: suspendBehaviorSelector
anchors.horizontalCenter: parent.horizontalCenter
model: ["Suspend", "Hibernate", "Suspend then Hibernate"]
selectionMode: "single"
checkEnabled: false
Connections {
target: powerCategory
function onCurrentIndexChanged() {
const behavior = powerCategory.currentIndex === 0 ? SettingsData.acSuspendBehavior : SettingsData.batterySuspendBehavior
suspendBehaviorSelector.currentIndex = behavior
}
}
}
Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout
const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
}
Component.onCompleted: {
const behavior = powerCategory.currentIndex === 0 ? SettingsData.acSuspendBehavior : SettingsData.batterySuspendBehavior
currentIndex = behavior
}
onValueChanged: value => {
const index = timeoutOptions.indexOf(value)
if (index >= 0) {
const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) {
SettingsData.setAcHibernateTimeout(timeout)
} else {
SettingsData.setBatteryHibernateTimeout(timeout)
onSelectionChanged: (index, selected) => {
if (selected) {
if (powerCategory.currentIndex === 0) {
SettingsData.set("acSuspendBehavior", index)
} else {
SettingsData.set("batterySuspendBehavior", index)
}
}
}
}
@@ -352,7 +358,7 @@ Item {
text: I18n.tr("Show Confirmation on Power Actions")
description: I18n.tr("Request confirmation on power off, restart, suspend, hibernate and logout actions")
checked: SettingsData.powerActionConfirm
onToggled: checked => SettingsData.setPowerActionConfirm(checked)
onToggled: checked => SettingsData.set("powerActionConfirm", checked)
}
}
}
@@ -418,7 +424,7 @@ Item {
}
onTextEdited: {
SettingsData.setCustomPowerActionLock(text.trim());
SettingsData.set("customPowerActionLock", text.trim());
}
}
}
@@ -450,7 +456,7 @@ Item {
}
onTextEdited: {
SettingsData.setCustomPowerActionLogout(text.trim());
SettingsData.set("customPowerActionLogout", text.trim());
}
}
}
@@ -482,7 +488,7 @@ Item {
}
onTextEdited: {
SettingsData.setCustomPowerActionSuspend(text.trim());
SettingsData.set("customPowerActionSuspend", text.trim());
}
}
}
@@ -514,7 +520,7 @@ Item {
}
onTextEdited: {
SettingsData.setCustomPowerActionHibernate(text.trim());
SettingsData.set("customPowerActionHibernate", text.trim());
}
}
}
@@ -546,7 +552,7 @@ Item {
}
onTextEdited: {
SettingsData.setCustomPowerActionReboot(text.trim());
SettingsData.set("customPowerActionReboot", text.trim());
}
}
}
@@ -578,7 +584,7 @@ Item {
}
onTextEdited: {
SettingsData.setCustomPowerActionPowerOff(text.trim());
SettingsData.set("customPowerActionPowerOff", text.trim());
}
}
}

View File

@@ -11,6 +11,8 @@ import qs.Widgets
DankModal {
id: settingsModal
layerNamespace: "dms:settings"
property Component settingsContent
property alias profileBrowser: profileBrowser
property int currentTabIndex: 0
@@ -34,8 +36,9 @@ DankModal {
}
objectName: "settingsModal"
width: 800
height: 800
width: Math.min(800, screenWidth * 0.9)
height: Math.min(800, screenHeight * 0.85)
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
visible: false
onBackgroundClicked: () => {
return hide();

View File

@@ -55,87 +55,96 @@ Rectangle {
width: 270
height: parent.height
color: Theme.surfaceContainer
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
Column {
DankFlickable {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
anchors.topMargin: Theme.spacingM + 2
spacing: Theme.spacingXS
clip: true
contentHeight: sidebarColumn.implicitHeight
ProfileSection {
parentModal: sidebarContainer.parentModal
}
Column {
id: sidebarColumn
Rectangle {
width: parent.width - Theme.spacingS * 2
height: 1
color: Theme.outline
opacity: 0.2
}
Item {
width: parent.width
height: Theme.spacingL
}
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
anchors.topMargin: Theme.spacingM + 2
spacing: Theme.spacingXS
Repeater {
id: sidebarRepeater
model: sidebarContainer.sidebarItems
delegate: Rectangle {
required property int index
required property var modelData
property bool isActive: sidebarContainer.currentIndex === index
ProfileSection {
parentModal: sidebarContainer.parentModal
}
Rectangle {
width: parent.width - Theme.spacingS * 2
height: 44
radius: Theme.cornerRadius
color: isActive ? Theme.primary : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
height: 1
color: Theme.outline
opacity: 0.2
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Item {
width: parent.width
height: Theme.spacingL
}
DankIcon {
name: modelData.icon || ""
size: Theme.iconSize - 2
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
Repeater {
id: sidebarRepeater
model: sidebarContainer.sidebarItems
delegate: Rectangle {
required property int index
required property var modelData
property bool isActive: sidebarContainer.currentIndex === index
width: sidebarColumn.width - Theme.spacingS * 2
height: 44
radius: Theme.cornerRadius
color: isActive ? Theme.primary : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: modelData.icon || ""
size: Theme.iconSize - 2
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.text || ""
font.pixelSize: Theme.fontSizeMedium
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: modelData.text || ""
font.pixelSize: Theme.fontSizeMedium
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter
MouseArea {
id: tabMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
sidebarContainer.currentIndex = index;
}
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
MouseArea {
id: tabMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
sidebarContainer.currentIndex = index;
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}

View File

@@ -0,0 +1,237 @@
import QtQuick
import Quickshell.Io
import qs.Services
Item {
id: controller
property string searchQuery: ""
property alias model: fileModel
property int selectedIndex: 0
property bool keyboardNavigationActive: false
property bool isSearching: false
property int totalResults: 0
property string searchField: "filename"
signal searchCompleted
ListModel {
id: fileModel
}
function performSearch() {
if (!DSearchService.dsearchAvailable) {
model.clear()
totalResults = 0
isSearching = false
return
}
if (searchQuery.length === 0) {
model.clear()
totalResults = 0
isSearching = false
return
}
isSearching = true
const params = {
"limit": 50,
"fuzzy": true,
"sort": "score",
"desc": true
}
if (searchField && searchField !== "all") {
params.field = searchField
}
DSearchService.search(searchQuery, params, response => {
if (response.error) {
model.clear()
totalResults = 0
isSearching = false
return
}
if (response.result) {
updateModel(response.result)
}
isSearching = false
searchCompleted()
})
}
function updateModel(result) {
model.clear()
totalResults = result.total_hits || 0
selectedIndex = 0
keyboardNavigationActive = true
if (!result.hits || result.hits.length === 0) {
selectedIndex = -1
keyboardNavigationActive = false
return
}
for (var i = 0; i < result.hits.length; i++) {
const hit = result.hits[i]
const filePath = hit.id || ""
const fileName = getFileName(filePath)
const fileExt = getFileExtension(fileName)
const fileType = determineFileType(fileName, filePath)
const dirPath = getDirPath(filePath)
model.append({
"filePath": filePath,
"fileName": fileName,
"fileExtension": fileExt,
"fileType": fileType,
"dirPath": dirPath,
"score": hit.score || 0
})
}
}
function getFileName(path) {
const parts = path.split('/')
return parts[parts.length - 1] || path
}
function getFileExtension(fileName) {
const parts = fileName.split('.')
if (parts.length > 1) {
return parts[parts.length - 1].toLowerCase()
}
return ""
}
function getDirPath(path) {
const lastSlash = path.lastIndexOf('/')
if (lastSlash > 0) {
return path.substring(0, lastSlash)
}
return ""
}
function determineFileType(fileName, filePath) {
const ext = getFileExtension(fileName)
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
if (imageExts.includes(ext)) {
return "image"
}
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
if (videoExts.includes(ext)) {
return "video"
}
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
if (audioExts.includes(ext)) {
return "audio"
}
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
if (codeExts.includes(ext)) {
return "code"
}
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
if (docExts.includes(ext)) {
return "document"
}
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
if (archiveExts.includes(ext)) {
return "archive"
}
if (!ext || fileName.indexOf('.') === -1) {
return "binary"
}
return "file"
}
function selectNext() {
if (model.count === 0) {
return
}
keyboardNavigationActive = true
selectedIndex = Math.min(selectedIndex + 1, model.count - 1)
}
function selectPrevious() {
if (model.count === 0) {
return
}
keyboardNavigationActive = true
selectedIndex = Math.max(selectedIndex - 1, 0)
}
signal fileOpened
function openFile(filePath) {
if (!filePath || filePath.length === 0) {
return
}
let url = filePath
if (!url.startsWith("file://")) {
url = "file://" + filePath
}
Qt.openUrlExternally(url)
fileOpened()
}
function openFolder(filePath) {
if (!filePath || filePath.length === 0) {
return
}
const lastSlash = filePath.lastIndexOf('/')
if (lastSlash <= 0) {
return
}
const dirPath = filePath.substring(0, lastSlash)
let url = dirPath
if (!url.startsWith("file://")) {
url = "file://" + dirPath
}
Qt.openUrlExternally(url)
fileOpened()
}
function openSelected() {
if (model.count === 0 || selectedIndex < 0 || selectedIndex >= model.count) {
return
}
const item = model.get(selectedIndex)
if (item && item.filePath) {
openFile(item.filePath)
}
}
function reset() {
searchQuery = ""
model.clear()
selectedIndex = -1
keyboardNavigationActive = false
isSearching = false
totalResults = 0
}
onSearchQueryChanged: {
performSearch()
}
onSearchFieldChanged: {
performSearch()
}
}

View File

@@ -0,0 +1,155 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import qs.Common
import qs.Widgets
Rectangle {
id: entry
required property string filePath
required property string fileName
required property string fileExtension
required property string fileType
required property string dirPath
required property bool isSelected
required property int itemIndex
signal clicked()
readonly property int iconSize: 40
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingL
Item {
width: iconSize
height: iconSize
anchors.verticalCenter: parent.verticalCenter
Image {
id: imagePreview
anchors.fill: parent
source: fileType === "image" ? `file://${filePath}` : ""
fillMode: Image.PreserveAspectCrop
smooth: true
cache: true
asynchronous: true
visible: fileType === "image" && status === Image.Ready
sourceSize.width: 128
sourceSize.height: 128
}
MultiEffect {
anchors.fill: parent
source: imagePreview
maskEnabled: true
maskSource: imageMask
visible: fileType === "image" && imagePreview.status === Image.Ready
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: imageMask
width: iconSize
height: iconSize
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
Rectangle {
anchors.fill: parent
radius: width / 2
color: getFileTypeColor()
visible: fileType !== "image" || imagePreview.status !== Image.Ready
StyledText {
anchors.centerIn: parent
text: getFileIconText()
font.pixelSize: fileExtension.length > 0 ? (fileExtension.length > 3 ? Theme.fontSizeSmall - 2 : Theme.fontSizeSmall) : Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Bold
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - iconSize - Theme.spacingL
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: fileName
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideMiddle
wrapMode: Text.NoWrap
maximumLineCount: 1
}
StyledText {
width: parent.width
text: dirPath
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideMiddle
maximumLineCount: 1
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: entry.clicked()
}
function getFileTypeColor() {
switch (fileType) {
case "code":
return Theme.codeFileColor || Theme.primarySelected
case "document":
return Theme.docFileColor || Theme.secondarySelected
case "video":
return Theme.videoFileColor || Theme.tertiarySelected
case "audio":
return Theme.audioFileColor || Theme.errorSelected
case "archive":
return Theme.archiveFileColor || Theme.warningSelected
case "binary":
return Theme.binaryFileColor || Theme.surfaceDim
default:
return Theme.surfaceLight
}
}
function getFileIconText() {
if (fileType === "binary") {
return "bin"
}
if (fileExtension.length > 0) {
return fileExtension
}
return fileName.charAt(0).toUpperCase()
}
}

View File

@@ -0,0 +1,246 @@
import QtQuick
import QtQuick.Effects
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: resultsContainer
property var fileSearchController: null
function resetScroll() {
filesList.contentY = 0
}
color: "transparent"
clip: true
DankListView {
id: filesList
property int itemHeight: 60
property int itemSpacing: Theme.spacingS
property bool hoverUpdatesSelection: false
property bool keyboardNavigationActive: fileSearchController ? fileSearchController.keyboardNavigationActive : false
signal keyboardNavigationReset
signal itemClicked(int index)
signal itemRightClicked(int index)
function ensureVisible(index) {
if (index < 0 || index >= count)
return
const itemY = index * (itemHeight + itemSpacing)
const itemBottom = itemY + itemHeight
if (itemY < contentY)
contentY = itemY
else if (itemBottom > contentY + height)
contentY = itemBottom - height
}
anchors.fill: parent
anchors.margins: Theme.spacingS
model: fileSearchController ? fileSearchController.model : null
currentIndex: fileSearchController ? fileSearchController.selectedIndex : -1
clip: true
spacing: itemSpacing
focus: true
interactive: true
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
reuseItems: true
onCurrentIndexChanged: {
if (keyboardNavigationActive)
ensureVisible(currentIndex)
}
onItemClicked: function (index) {
if (fileSearchController) {
const item = fileSearchController.model.get(index)
fileSearchController.openFile(item.filePath)
}
}
onItemRightClicked: function (index) {
if (fileSearchController) {
const item = fileSearchController.model.get(index)
fileSearchController.openFolder(item.filePath)
}
}
onKeyboardNavigationReset: {
if (fileSearchController)
fileSearchController.keyboardNavigationActive = false
}
delegate: Rectangle {
required property int index
required property string filePath
required property string fileName
required property string fileExtension
required property string fileType
required property string dirPath
width: ListView.view.width
height: filesList.itemHeight
radius: Theme.cornerRadius
color: ListView.isCurrentItem ? Theme.primaryPressed : fileMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingL
Item {
width: 40
height: 40
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: iconBackground
anchors.fill: parent
radius: width / 2
color: Theme.surfaceLight
visible: fileType !== "image"
DankNFIcon {
id: nerdIcon
anchors.centerIn: parent
name: {
const lowerName = fileName.toLowerCase()
if (lowerName.startsWith("dockerfile"))
return "docker"
if (lowerName.startsWith("makefile"))
return "makefile"
if (lowerName.startsWith("license"))
return "license"
if (lowerName.startsWith("readme"))
return "readme"
return fileExtension.toLowerCase()
}
size: Theme.fontSizeXLarge
color: Theme.surfaceText
}
StyledText {
anchors.centerIn: parent
text: fileExtension ? (fileExtension.length > 4 ? fileExtension.substring(0, 4) : fileExtension) : "?"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Bold
visible: !nerdIcon.visible
}
}
Loader {
anchors.fill: parent
active: fileType === "image"
sourceComponent: Image {
anchors.fill: parent
source: "file://" + filePath
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
maskSource: ShaderEffectSource {
sourceItem: Rectangle {
width: 40
height: 40
radius: 20
}
}
}
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 40 - Theme.spacingL
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: fileName || ""
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideMiddle
maximumLineCount: 1
}
StyledText {
width: parent.width
text: dirPath || ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideMiddle
maximumLineCount: 1
}
}
}
MouseArea {
id: fileMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
z: 10
onEntered: {
if (filesList.hoverUpdatesSelection && !filesList.keyboardNavigationActive)
filesList.currentIndex = index
}
onPositionChanged: {
filesList.keyboardNavigationReset()
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
filesList.itemClicked(index)
} else if (mouse.button === Qt.RightButton) {
filesList.itemRightClicked(index)
}
}
}
}
}
Item {
anchors.fill: parent
visible: !fileSearchController || !fileSearchController.model || fileSearchController.model.count === 0
StyledText {
property string displayText: {
if (!fileSearchController) {
return ""
}
if (!DSearchService.dsearchAvailable) {
return I18n.tr("DankSearch not available")
}
if (fileSearchController.isSearching) {
return I18n.tr("Searching...")
}
if (fileSearchController.searchQuery.length === 0) {
return I18n.tr("Enter a search query")
}
if (!fileSearchController.model || fileSearchController.model.count === 0) {
return I18n.tr("No files found")
}
return ""
}
text: displayText
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: displayText.length > 0
}
}
}

View File

@@ -11,10 +11,40 @@ Item {
property alias appLauncher: appLauncher
property alias searchField: searchField
property alias fileSearchController: fileSearchController
property var parentModal: null
property string searchMode: "apps"
function resetScroll() {
resultsView.resetScroll()
if (searchMode === "apps") {
resultsView.resetScroll()
} else {
fileSearchResults.resetScroll()
}
}
function updateSearchMode() {
if (searchField.text.startsWith("/")) {
if (searchMode !== "files") {
searchMode = "files"
}
const query = searchField.text.substring(1)
fileSearchController.searchQuery = query
} else {
if (searchMode !== "apps") {
searchMode = "apps"
fileSearchController.reset()
appLauncher.searchQuery = searchField.text
}
}
}
onSearchModeChanged: {
if (searchMode === "files") {
appLauncher.keyboardNavigationActive = false
} else {
fileSearchController.keyboardNavigationActive = false
}
}
anchors.fill: parent
@@ -27,59 +57,95 @@ Item {
event.accepted = true
} else if (event.key === Qt.Key_Down) {
appLauncher.selectNext()
if (searchMode === "apps") {
appLauncher.selectNext()
} else {
fileSearchController.selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_Up) {
appLauncher.selectPrevious()
if (searchMode === "apps") {
appLauncher.selectPrevious()
} else {
fileSearchController.selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") {
} else if (event.key === Qt.Key_Right && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
event.accepted = true
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
} else if (event.key === Qt.Key_Left && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
event.accepted = true
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNext()
if (searchMode === "apps") {
appLauncher.selectNext()
} else {
fileSearchController.selectNext()
}
event.accepted = true
} else if (event.key == Qt.Key_K && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
if (searchMode === "apps") {
appLauncher.selectPrevious()
} else {
fileSearchController.selectPrevious()
}
event.accepted = true
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
event.accepted = true
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
event.accepted = true
} else if (event.key === Qt.Key_Tab) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
if (searchMode === "apps") {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
} else {
appLauncher.selectNext()
}
} else {
appLauncher.selectNext()
fileSearchController.selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_Backtab) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
if (searchMode === "apps") {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
} else {
appLauncher.selectPrevious()
}
} else {
appLauncher.selectPrevious()
fileSearchController.selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
if (searchMode === "apps") {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
} else {
appLauncher.selectNext()
}
} else {
appLauncher.selectNext()
fileSearchController.selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
if (searchMode === "apps") {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
} else {
appLauncher.selectPrevious()
}
} else {
appLauncher.selectPrevious()
fileSearchController.selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected()
if (searchMode === "apps") {
appLauncher.launchSelected()
} else if (searchMode === "files") {
fileSearchController.openSelected()
}
event.accepted = true
}
}
@@ -94,10 +160,19 @@ Item {
parentModal.hide()
}
onViewModeSelected: mode => {
SettingsData.setSpotlightModalViewMode(mode)
SettingsData.set("spotlightModalViewMode", mode)
}
}
FileSearchController {
id: fileSearchController
onFileOpened: () => {
if (parentModal)
parentModal.hide()
}
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
@@ -115,10 +190,10 @@ Item {
width: parent.width - 80 - Theme.spacingL
height: 56
cornerRadius: Theme.cornerRadius
backgroundColor: Theme.surfaceContainerHigh
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
normalBorderColor: Theme.outlineMedium
focusedBorderColor: Theme.primary
leftIconName: "search"
leftIconName: searchMode === "files" ? "folder" : "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
@@ -130,10 +205,14 @@ Item {
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
ignoreTabKeys: true
keyForwardTargets: [spotlightKeyHandler]
text: appLauncher.searchQuery
onTextEdited: () => {
appLauncher.searchQuery = text
}
onTextChanged: {
if (searchMode === "apps") {
appLauncher.searchQuery = text
}
}
onTextEdited: {
updateSearchMode()
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
if (parentModal)
@@ -141,12 +220,18 @@ Item {
event.accepted = true
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0)
appLauncher.launchSelected()
else if (appLauncher.model.count > 0)
appLauncher.launchApp(appLauncher.model.get(0))
if (searchMode === "apps") {
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0)
appLauncher.launchSelected()
else if (appLauncher.model.count > 0)
appLauncher.launchApp(appLauncher.model.get(0))
} else if (searchMode === "files") {
if (fileSearchController.model.count > 0)
fileSearchController.openSelected()
}
event.accepted = true
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key
=== Qt.Key_Backtab || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
event.accepted = false
}
}
@@ -154,7 +239,7 @@ Item {
Row {
spacing: Theme.spacingXS
visible: appLauncher.model.count > 0
visible: searchMode === "apps" && appLauncher.model.count > 0
anchors.verticalCenter: parent.verticalCenter
Rectangle {
@@ -207,12 +292,116 @@ Item {
}
}
}
Row {
spacing: Theme.spacingXS
visible: searchMode === "files"
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: filenameFilterButton
width: 36
height: 36
radius: Theme.cornerRadius
color: fileSearchController.searchField === "filename" ? Theme.primaryHover : filenameFilterArea.containsMouse ? Theme.surfaceHover : "transparent"
DankIcon {
anchors.centerIn: parent
name: "title"
size: 18
color: fileSearchController.searchField === "filename" ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: filenameFilterArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
fileSearchController.searchField = "filename"
}
onEntered: {
filenameTooltipLoader.active = true
Qt.callLater(() => {
if (filenameTooltipLoader.item) {
const p = mapToItem(null, width / 2, height + Theme.spacingXS)
filenameTooltipLoader.item.show(I18n.tr("Search filenames"), p.x, p.y, null)
}
})
}
onExited: {
if (filenameTooltipLoader.item)
filenameTooltipLoader.item.hide()
filenameTooltipLoader.active = false
}
}
}
Rectangle {
id: contentFilterButton
width: 36
height: 36
radius: Theme.cornerRadius
color: fileSearchController.searchField === "body" ? Theme.primaryHover : contentFilterArea.containsMouse ? Theme.surfaceHover : "transparent"
DankIcon {
anchors.centerIn: parent
name: "description"
size: 18
color: fileSearchController.searchField === "body" ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: contentFilterArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
fileSearchController.searchField = "body"
}
onEntered: {
contentTooltipLoader.active = true
Qt.callLater(() => {
if (contentTooltipLoader.item) {
const p = mapToItem(null, width / 2, height + Theme.spacingXS)
contentTooltipLoader.item.show(I18n.tr("Search file contents"), p.x, p.y, null)
}
})
}
onExited: {
if (contentTooltipLoader.item)
contentTooltipLoader.item.hide()
contentTooltipLoader.active = false
}
}
}
}
}
SpotlightResults {
id: resultsView
appLauncher: spotlightKeyHandler.appLauncher
contextMenu: contextMenu
Item {
width: parent.width
height: parent.height - y
SpotlightResults {
id: resultsView
anchors.fill: parent
appLauncher: spotlightKeyHandler.appLauncher
contextMenu: contextMenu
visible: searchMode === "apps"
}
FileSearchResults {
id: fileSearchResults
anchors.fill: parent
fileSearchController: spotlightKeyHandler.fileSearchController
visible: searchMode === "files"
}
}
}
@@ -233,7 +422,6 @@ Item {
MouseArea {
// Prevent closing when clicking on the menu itself
x: contextMenu.x
y: contextMenu.y
width: contextMenu.width
@@ -241,4 +429,18 @@ Item {
onClicked: () => {}
}
}
Loader {
id: filenameTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: contentTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
}

View File

@@ -34,7 +34,7 @@ Popup {
background: Rectangle {
radius: Theme.cornerRadius
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1

View File

@@ -12,6 +12,8 @@ import qs.Widgets
DankModal {
id: spotlightModal
layerNamespace: "dms:spotlight"
property bool spotlightOpen: false
property alias spotlightContent: spotlightContentInstance
@@ -20,10 +22,10 @@ DankModal {
open()
Qt.callLater(() => {
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus()
}
})
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus()
}
})
}
function showWithQuery(query) {
@@ -40,10 +42,10 @@ DankModal {
open()
Qt.callLater(() => {
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus()
}
})
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus()
}
})
}
function hide() {
@@ -58,6 +60,9 @@ DankModal {
spotlightContent.appLauncher.selectedIndex = 0
spotlightContent.appLauncher.setCategory(I18n.tr("All"))
}
if (spotlightContent.fileSearchController) {
spotlightContent.fileSearchController.reset()
}
if (spotlightContent.resetScroll) {
spotlightContent.resetScroll()
}
@@ -76,9 +81,9 @@ DankModal {
}
shouldBeVisible: spotlightOpen
width: 550
height: 700
backgroundColor: Theme.popupBackground()
width: 500
height: 600
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium
borderWidth: 1
@@ -111,17 +116,17 @@ DankModal {
}
IpcHandler {
function open(): string {
function open(): string {
spotlightModal.show()
return "SPOTLIGHT_OPEN_SUCCESS"
}
function close(): string {
function close(): string {
spotlightModal.hide()
return "SPOTLIGHT_CLOSE_SUCCESS"
}
function toggle(): string {
function toggle(): string {
spotlightModal.toggle()
return "SPOTLIGHT_TOGGLE_SUCCESS"
}

View File

@@ -7,10 +7,6 @@ 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
@@ -19,8 +15,6 @@ Rectangle {
resultsGrid.contentY = 0
}
width: parent.width
height: parent.height - y
radius: Theme.cornerRadius
color: "transparent"
clip: true
@@ -79,115 +73,22 @@ Rectangle {
appLauncher.keyboardNavigationActive = false
}
delegate: Rectangle {
width: ListView.view.width
height: resultsList.itemHeight
radius: Theme.cornerRadius
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingL
Item {
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: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
asynchronous: true
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !parent.isMaterial && !listIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.primarySelected
StyledText {
anchors.centerIn: parent
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: resultsList.iconSize * 0.4
color: Theme.primary
font.weight: Font.Bold
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: model.name || ""
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
}
StyledText {
width: parent.width
text: model.comment || "Application"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideRight
maximumLineCount: 1
visible: resultsList.showDescription && model.comment && model.comment.length > 0
}
}
}
MouseArea {
id: listMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
z: 10
onEntered: () => {
if (resultsList.hoverUpdatesSelection && !resultsList.keyboardNavigationActive)
resultsList.currentIndex = index
}
onPositionChanged: () => {
resultsList.keyboardNavigationReset()
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
resultsList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton && !model.isPlugin) {
const globalPos = mapToItem(null, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y)
}
}
delegate: AppLauncherListDelegate {
listView: resultsList
itemHeight: resultsList.itemHeight
iconSize: resultsList.iconSize
showDescription: resultsList.showDescription
hoverUpdatesSelection: resultsList.hoverUpdatesSelection
keyboardNavigationActive: resultsList.keyboardNavigationActive
isCurrentItem: ListView.isCurrentItem
iconMaterialSizeAdjustment: 0
iconUnicodeScale: 0.8
onItemClicked: (idx, modelData) => resultsList.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
resultsList.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
}
onKeyboardNavigationReset: resultsList.keyboardNavigationReset
}
}
@@ -256,103 +157,23 @@ Rectangle {
appLauncher.keyboardNavigationActive = false
}
delegate: Rectangle {
width: resultsGrid.cellWidth - resultsGrid.cellPadding
height: resultsGrid.cellHeight - resultsGrid.cellPadding
radius: Theme.cornerRadius
color: resultsGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
Item {
property int iconSize: Math.min(resultsGrid.maxIconSize, Math.max(resultsGrid.minIconSize, resultsGrid.cellWidth * resultsGrid.iconSizeRatio))
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: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !parent.isMaterial && !gridIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.primarySelected
StyledText {
anchors.centerIn: parent
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: Math.min(28, parent.width * 0.5)
color: Theme.primary
font.weight: Font.Bold
}
}
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
width: resultsGrid.cellWidth - 12
text: model.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
maximumLineCount: 1
wrapMode: Text.NoWrap
}
}
MouseArea {
id: gridMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
z: 10
onEntered: () => {
if (resultsGrid.hoverUpdatesSelection && !resultsGrid.keyboardNavigationActive)
resultsGrid.currentIndex = index
}
onPositionChanged: () => {
resultsGrid.keyboardNavigationReset()
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
resultsGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton && !model.isPlugin) {
const globalPos = mapToItem(null, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y)
}
}
delegate: AppLauncherGridDelegate {
gridView: resultsGrid
cellWidth: resultsGrid.cellWidth
cellHeight: resultsGrid.cellHeight
cellPadding: resultsGrid.cellPadding
minIconSize: resultsGrid.minIconSize
maxIconSize: resultsGrid.maxIconSize
iconSizeRatio: resultsGrid.iconSizeRatio
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
currentIndex: resultsGrid.currentIndex
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
resultsGrid.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
}
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
}
}
}

View File

@@ -7,6 +7,8 @@ import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:wifi-password"
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
property string wifiUsernameInput: ""
@@ -21,6 +23,11 @@ DankModal {
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 = ""
@@ -32,6 +39,10 @@ DankModal {
promptReason = ""
promptFields = []
promptSetting = ""
isVpnPrompt = false
connectionName = ""
vpnServiceType = ""
connectionType = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
@@ -48,13 +59,18 @@ DankModal {
})
}
function showFromPrompt(token, ssid, setting, fields, hints, reason) {
wifiPasswordSSID = ssid
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"
@@ -163,7 +179,12 @@ 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
@@ -175,6 +196,9 @@ DankModal {
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
}
@@ -218,7 +242,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
@@ -271,7 +295,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
@@ -281,7 +305,9 @@ DankModal {
onAccepted: () => {
if (isPromptMode) {
const secrets = {}
if (promptSetting === "802-11-wireless-security") {
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
@@ -340,7 +366,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -372,7 +398,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -495,7 +521,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 {
@@ -518,7 +549,9 @@ DankModal {
onClicked: () => {
if (isPromptMode) {
const secrets = {}
if (promptSetting === "802-11-wireless-security") {
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

View File

@@ -13,6 +13,8 @@ import qs.Widgets
DankPopout {
id: appDrawerPopout
layerNamespace: "dms:app-launcher"
property var triggerScreen: null
// Setting to Exclusive, so virtual keyboards can send input to app drawer
@@ -59,7 +61,7 @@ DankPopout {
gridColumns: 4
onAppLaunched: appDrawerPopout.close()
onViewModeSelected: function (mode) {
SettingsData.setAppLauncherViewMode(mode)
SettingsData.set("appLauncherViewMode", mode)
}
}
@@ -69,7 +71,7 @@ DankPopout {
property alias searchField: searchField
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
antialiasing: true
smooth: true
@@ -208,7 +210,7 @@ DankPopout {
anchors.horizontalCenter: parent.horizontalCenter
height: 52
cornerRadius: Theme.cornerRadius
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
backgroundColor: Theme.withAlpha(Theme.surfaceVariant, Theme.popupTransparency)
normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
focusedBorderColor: Theme.primary
leftIconName: "search"
@@ -389,124 +391,27 @@ DankPopout {
appLauncher.keyboardNavigationActive = false
}
delegate: Rectangle {
width: ListView.view.width
height: appList.itemHeight
radius: Theme.cornerRadius
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingL
Item {
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: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
visible: !parent.isMaterial && listIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0
border.color: Theme.primarySelected
StyledText {
anchors.centerIn: parent
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: appList.iconSize * 0.4
color: Theme.primary
font.weight: Font.Bold
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: model.name || ""
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
}
StyledText {
width: parent.width
text: model.comment || "Application"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideRight
maximumLineCount: 1
visible: appList.showDescription && model.comment && model.comment.length > 0
}
}
}
MouseArea {
id: listMouseArea
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
z: 10
onEntered: {
if (appList.hoverUpdatesSelection && !appList.keyboardNavigationActive)
appList.currentIndex = index
}
onPositionChanged: {
appList.keyboardNavigationReset()
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
appList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) {
var globalPos = mapToItem(null, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
}
}
delegate: AppLauncherListDelegate {
listView: appList
itemHeight: appList.itemHeight
iconSize: appList.iconSize
showDescription: appList.showDescription
hoverUpdatesSelection: appList.hoverUpdatesSelection
keyboardNavigationActive: appList.keyboardNavigationActive
isCurrentItem: ListView.isCurrentItem
mouseAreaLeftMargin: Theme.spacingS
mouseAreaRightMargin: Theme.spacingS
mouseAreaBottomMargin: Theme.spacingM
iconMargins: Theme.spacingXS
iconFallbackLeftMargin: Theme.spacingS
iconFallbackRightMargin: Theme.spacingS
iconFallbackBottomMargin: Theme.spacingM
onItemClicked: (idx, modelData) => appList.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const panelPos = contextMenu.parent.mapFromItem(null, mouseX, mouseY)
appList.itemRightClicked(idx, modelData, panelPos.x, panelPos.y)
}
onKeyboardNavigationReset: appList.keyboardNavigationReset
}
}
@@ -577,112 +482,30 @@ DankPopout {
appLauncher.keyboardNavigationActive = false
}
delegate: Rectangle {
width: appGrid.cellWidth - appGrid.cellPadding
height: appGrid.cellHeight - appGrid.cellPadding
radius: Theme.cornerRadius
color: appGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
Item {
property int iconSize: Math.min(appGrid.maxIconSize, Math.max(appGrid.minIconSize, appGrid.cellWidth * appGrid.iconSizeRatio))
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
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0
border.color: Theme.primarySelected
StyledText {
anchors.centerIn: parent
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: Math.min(28, parent.width * 0.5)
color: Theme.primary
font.weight: Font.Bold
}
}
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
width: appGrid.cellWidth - 12
text: model.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
maximumLineCount: 1
wrapMode: Text.NoWrap
}
}
MouseArea {
id: gridMouseArea
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
z: 10
onEntered: {
if (appGrid.hoverUpdatesSelection && !appGrid.keyboardNavigationActive)
appGrid.currentIndex = index
}
onPositionChanged: {
appGrid.keyboardNavigationReset()
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
appGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) {
var globalPos = mapToItem(null, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
}
}
delegate: AppLauncherGridDelegate {
gridView: appGrid
cellWidth: appGrid.cellWidth
cellHeight: appGrid.cellHeight
cellPadding: appGrid.cellPadding
minIconSize: appGrid.minIconSize
maxIconSize: appGrid.maxIconSize
iconSizeRatio: appGrid.iconSizeRatio
hoverUpdatesSelection: appGrid.hoverUpdatesSelection
keyboardNavigationActive: appGrid.keyboardNavigationActive
currentIndex: appGrid.currentIndex
mouseAreaLeftMargin: Theme.spacingS
mouseAreaRightMargin: Theme.spacingS
mouseAreaBottomMargin: Theme.spacingS
iconFallbackLeftMargin: Theme.spacingS
iconFallbackRightMargin: Theme.spacingS
iconFallbackBottomMargin: Theme.spacingS
iconMaterialSizeAdjustment: Theme.spacingL
onItemClicked: (idx, modelData) => appGrid.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const panelPos = contextMenu.parent.mapFromItem(null, mouseX, mouseY)
appGrid.itemRightClicked(idx, modelData, panelPos.x, panelPos.y)
}
onKeyboardNavigationReset: appGrid.keyboardNavigationReset
}
}
}
@@ -719,7 +542,7 @@ DankPopout {
background: Rectangle {
radius: Theme.cornerRadius
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1

View File

@@ -42,7 +42,7 @@ Item {
height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
StyledText {
anchors.centerIn: parent
@@ -81,7 +81,7 @@ Item {
height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
StyledText {
@@ -117,7 +117,7 @@ Item {
height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
StyledText {

View File

@@ -0,0 +1,233 @@
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
import qs.Services
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
}
}
Component.onCompleted: {
if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source
setWallpaperImmediate(formattedSource)
}
isInitialized = true
}
property bool isInitialized: false
property real transitionProgress: 0
readonly property bool transitioning: transitionAnimation.running
onSourceChanged: {
const isColor = source.startsWith("#")
if (!source) {
setWallpaperImmediate("")
} else if (isColor) {
setWallpaperImmediate("")
} else {
if (!isInitialized || !currentWallpaper.source) {
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source)
isInitialized = true
} else if (CompositorService.isNiri && SessionData.isSwitchingMode) {
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source)
} else {
changeWallpaper(source.startsWith("file://") ? source : "file://" + source)
}
}
}
function setWallpaperImmediate(newSource) {
transitionAnimation.stop()
root.transitionProgress = 0.0
currentWallpaper.source = newSource
nextWallpaper.source = ""
currentWallpaper.opacity = 1
nextWallpaper.opacity = 0
}
function changeWallpaper(newPath) {
if (newPath === currentWallpaper.source)
return
if (!newPath || newPath.startsWith("#"))
return
if (root.transitioning) {
transitionAnimation.stop()
root.transitionProgress = 0
currentWallpaper.source = nextWallpaper.source
nextWallpaper.source = ""
}
if (!currentWallpaper.source) {
setWallpaperImmediate(newPath)
return
}
nextWallpaper.source = newPath
if (nextWallpaper.status === Image.Ready) {
transitionAnimation.start()
}
}
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
asynchronous: true
sourceComponent: DankBackdrop {
screenName: modelData.name
}
}
Image {
id: currentWallpaper
anchors.fill: parent
visible: false
opacity: 1
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
}
Image {
id: nextWallpaper
anchors.fill: parent
visible: false
opacity: 0
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
onStatusChanged: {
if (status !== Image.Ready)
return
if (!root.transitioning) {
transitionAnimation.start()
}
}
}
Item {
id: blurredLayer
anchors.fill: parent
MultiEffect {
anchors.fill: parent
source: currentWallpaper
blurEnabled: true
blur: 0.8
blurMax: 75
opacity: 1 - root.transitionProgress
autoPaddingEnabled: false
}
MultiEffect {
anchors.fill: parent
source: nextWallpaper
blurEnabled: true
blur: 0.8
blurMax: 75
opacity: root.transitionProgress
autoPaddingEnabled: false
}
}
NumberAnimation {
id: transitionAnimation
target: root
property: "transitionProgress"
from: 0.0
to: 1.0
duration: 1000
easing.type: Easing.InOutCubic
onFinished: {
Qt.callLater(() => {
if (nextWallpaper.source && nextWallpaper.status === Image.Ready && !nextWallpaper.source.toString().startsWith("#")) {
currentWallpaper.source = nextWallpaper.source
}
nextWallpaper.source = ""
currentWallpaper.opacity = 1
nextWallpaper.opacity = 0
root.transitionProgress = 0.0
})
}
}
}
}
}

View File

@@ -0,0 +1,309 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
Ref {
service: CupsService
}
ccWidgetIcon: CupsService.cupsAvailable && CupsService.getPrintersNum() > 0 ? "print" : "print_disabled"
ccWidgetPrimaryText: I18n.tr("Printers")
ccWidgetSecondaryText: {
if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) {
return I18n.tr("Printers: ") + CupsService.getPrintersNum() + " - " + I18n.tr("Jobs: ") + CupsService.getTotalJobsNum()
} else {
if (!CupsService.cupsAvailable) {
return I18n.tr("Print Server not available")
} else {
return I18n.tr("No printer found")
}
}
}
ccWidgetIsActive: CupsService.cupsAvailable && CupsService.getTotalJobsNum() > 0
onCcWidgetToggled: {
}
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
visible: !CupsService.cupsAvailable || CupsService.getPrintersNum() == 0
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "print_disabled"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: !CupsService.cupsAvailable ? I18n.tr("Print Server not available") : I18n.tr("No printer found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
visible: CupsService.cupsAvailable && CupsService.getPrintersNum() > 0
height: visible ? 120 : 0
RowLayout {
spacing: Theme.spacingS
width: parent.width
DankDropdown {
id: printerDropdown
text: ""
Layout.fillWidth: true
Layout.maximumWidth: parent.width - 180
description: ""
currentValue: {
CupsService.getSelectedPrinter()
}
options: CupsService.getPrintersNames()
onValueChanged: value => {
CupsService.setSelectedPrinter(value)
}
}
Column {
spacing: Theme.spacingS
StyledText {
text: CupsService.getCurrentPrinterStatePrettyShort()
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
Row {
spacing: Theme.spacingM
Rectangle {
height: 24
width: 80
radius: 14
color: printerStatusToggle.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: true
opacity: 1.0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: CupsService.getCurrentPrinterState() === "stopped" ? "play_arrow" : "pause"
size: Theme.fontSizeSmall + 4
color: Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: CupsService.getCurrentPrinterState() === "stopped" ? I18n.tr("Resume") : I18n.tr("Pause")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: printerStatusToggle
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: true
onClicked: {
const selected = CupsService.getSelectedPrinter()
if (CupsService.getCurrentPrinterState() === "stopped") {
CupsService.resumePrinter(selected)
} else {
CupsService.pausePrinter(selected)
}
}
}
}
Rectangle {
height: 24
width: 80
radius: 14
color: clearJobsToggle.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: true
opacity: 1.0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "delete_forever"
size: Theme.fontSizeSmall + 4
color: Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Jobs")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: clearJobsToggle
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: true
onClicked: {
const selected = CupsService.getSelectedPrinter()
CupsService.purgeJobs(selected)
}
}
}
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: 120
visible: CupsService.getCurrentPrinterJobs().length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "work"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("The job queue of this printer is empty")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: CupsService.getCurrentPrinterJobs()
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
border.width: 1
border.color: Theme.outlineLight
opacity: 1.0
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
DankIcon {
name: "docs"
size: Theme.iconSize + 2
color: Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
StyledText {
text: "[" + modelData.id + "] " + modelData.state + " (" + (modelData.size / 1024) + "kb)"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
text: {
var date = new Date(modelData.timeCreated)
return date.toLocaleString(Qt.locale(), Locale.ShortFormat)
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
}
}
DankActionButton {
id: cancelJobButton
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
iconName: "delete"
buttonSize: 36
onClicked: {
CupsService.cancelJob(CupsService.getSelectedPrinter(), modelData.id)
}
}
}
}
}
}
}
}
}
}

View File

@@ -10,27 +10,24 @@ 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)
}
DMSNetworkService.toggleVpn()
}
ccDetailContent: Component {
@@ -38,7 +35,7 @@ PluginComponent {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
Column {
id: detailColumn
@@ -52,9 +49,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)
@@ -62,19 +59,20 @@ PluginComponent {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Item {
elide: Text.ElideRight
wrapMode: Text.NoWrap
Layout.fillWidth: true
Layout.maximumWidth: parent.width - 120
}
Rectangle {
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
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
Row {
anchors.centerIn: parent
@@ -98,8 +96,9 @@ PluginComponent {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
@@ -123,7 +122,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 +153,7 @@ PluginComponent {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -162,9 +161,10 @@ 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
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
RowLayout {
anchors.left: parent.left
@@ -174,20 +174,24 @@ 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
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
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
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
@@ -233,8 +237,9 @@ PluginComponent {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}

View File

@@ -25,7 +25,7 @@ Rectangle {
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive:
Theme.surfaceContainerHigh
Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)

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
@@ -49,7 +50,7 @@ Item {
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
SettingsData.set("controlCenterWidgets", newWidgets)
if (root.collapseCallback) {
root.collapseCallback()
}
@@ -73,7 +74,7 @@ Item {
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
SettingsData.set("controlCenterWidgets", newWidgets)
if (root.collapseCallback) {
root.collapseCallback()
}
@@ -103,6 +104,12 @@ Item {
}
builtinInstance = widgetModel.vpnBuiltinInstance
}
if (builtinId === "builtin_cups") {
if (widgetModel?.cupsLoader) {
widgetModel.cupsLoader.active = true
}
builtinInstance = widgetModel.cupsBuiltinInstance
}
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return
@@ -205,8 +212,9 @@ Item {
Component {
id: brightnessDetailComponent
BrightnessDetail {
currentDeviceName: root.expandedWidgetData?.deviceName || ""
initialDeviceName: root.expandedWidgetData?.deviceName || ""
instanceId: root.expandedWidgetData?.instanceId || ""
screenName: root.screenName
}
}
}

View File

@@ -83,7 +83,7 @@ Item {
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
SettingsData.set("controlCenterWidgets", newWidgets)
}
}
}

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
}
}
}
@@ -216,9 +219,13 @@ Column {
{
if (NetworkService.wifiToggling)
return "sync"
if (NetworkService.networkStatus === "ethernet")
const status = NetworkService.networkStatus
if (status === "ethernet")
return "settings_ethernet"
if (NetworkService.networkStatus === "wifi")
if (status === "vpn")
return NetworkService.ethernetConnected ? "settings_ethernet" : NetworkService.wifiSignalIcon
if (status === "wifi")
return NetworkService.wifiSignalIcon
if (NetworkService.wifiEnabled)
return "wifi_off"
@@ -263,9 +270,17 @@ Column {
{
if (NetworkService.wifiToggling)
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
if (NetworkService.networkStatus === "ethernet")
const status = NetworkService.networkStatus
if (status === "ethernet")
return "Ethernet"
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID)
if (status === "vpn") {
if (NetworkService.ethernetConnected)
return "Ethernet"
if (NetworkService.wifiConnected && NetworkService.currentWifiSSID)
return NetworkService.currentWifiSSID
}
if (status === "wifi" && NetworkService.currentWifiSSID)
return NetworkService.currentWifiSSID
if (NetworkService.wifiEnabled)
return "Not connected"
@@ -295,9 +310,17 @@ Column {
{
if (NetworkService.wifiToggling)
return "Please wait..."
if (NetworkService.networkStatus === "ethernet")
const status = NetworkService.networkStatus
if (status === "ethernet")
return "Connected"
if (NetworkService.networkStatus === "wifi")
if (status === "vpn") {
if (NetworkService.ethernetConnected)
return "Connected"
if (NetworkService.wifiConnected)
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
}
if (status === "wifi")
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
if (NetworkService.wifiEnabled)
return "Select network"
@@ -329,7 +352,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":
{
@@ -337,7 +363,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 || ""
@@ -349,9 +378,13 @@ Column {
{
if (NetworkService.wifiToggling)
return false
if (NetworkService.networkStatus === "ethernet")
const status = NetworkService.networkStatus
if (status === "ethernet")
return true
if (NetworkService.networkStatus === "wifi")
if (status === "vpn")
return NetworkService.ethernetConnected || NetworkService.wifiConnected
if (status === "wifi")
return true
return NetworkService.wifiEnabled
}
@@ -452,7 +485,7 @@ Column {
anchors.centerIn: parent
width: parent.width
height: 14
property color sliderTrackColor: Theme.surfaceContainerHigh
property color sliderTrackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
}
}
}
@@ -472,7 +505,9 @@ Column {
height: 14
deviceName: widgetData.deviceName || ""
instanceId: widgetData.instanceId || ""
property color sliderTrackColor: Theme.surfaceContainerHigh
screenName: root.screenName
parentScreen: root.parentScreen
property color sliderTrackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onIconClicked: {
if (!root.editMode && DisplayService.devices && DisplayService.devices.length > 1) {
@@ -495,7 +530,7 @@ Column {
anchors.centerIn: parent
width: parent.width
height: 14
property color sliderTrackColor: Theme.surfaceContainerHigh
property color sliderTrackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
}
}
}
@@ -759,6 +794,12 @@ Column {
}
builtinInstance = Qt.binding(() => root.model?.vpnBuiltinInstance)
}
if (id === "builtin_cups") {
if (root.model?.cupsLoader) {
root.model.cupsLoader.active = true
}
builtinInstance = Qt.binding(() => root.model?.cupsBuiltinInstance)
}
}
sourceComponent: {
@@ -801,6 +842,9 @@ Column {
onExpandClicked: {
if (root.editMode)
return
if (builtinInstance) {
builtinInstance.ccWidgetExpanded()
}
root.expandClicked(widgetData, widgetIndex)
}
}
@@ -934,6 +978,9 @@ Column {
onExpandClicked: {
if (root.editMode)
return
if (pluginInstance) {
pluginInstance.ccWidgetExpanded()
}
root.expandClicked(widgetData, widgetIndex)
}
}

View File

@@ -110,7 +110,7 @@ Item {
copy[i] = copy[j];
copy[j] = tmp;
SettingsData.setControlCenterWidgets(copy);
SettingsData.set("controlCenterWidgets", copy);
}
function snapToGrid() {
@@ -244,7 +244,7 @@ Item {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = newSize
SettingsData.setControlCenterWidgets(widgets)
SettingsData.set("controlCenterWidgets", widgets)
}
}
}

View File

@@ -76,7 +76,7 @@ Row {
width: 400 - Theme.spacingL * 2
height: 50
radius: Theme.cornerRadius
color: widgetMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceContainerHigh
color: widgetMouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0

View File

@@ -15,7 +15,7 @@ Rectangle {
implicitHeight: 70
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 0

View File

@@ -20,6 +20,8 @@ import "./utils/state.js" as StateUtils
DankPopout {
id: root
layerNamespace: "dms:control-center"
property string expandedSection: ""
property var triggerScreen: null
property bool editMode: false
@@ -46,7 +48,7 @@ DankPopout {
}
}
readonly property color _containerBg: Theme.surfaceContainerHigh
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
function setTriggerPosition(x, y, width, section, screen) {
StateUtils.setTriggerPosition(root, x, y, width, section, screen)
@@ -154,6 +156,8 @@ DankPopout {
model: widgetModel
bluetoothCodecSelector: bluetoothCodecSelector
colorPickerModal: root.colorPickerModal
screenName: root.triggerScreen?.name || ""
parentScreen: root.triggerScreen
onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData

View File

@@ -14,7 +14,7 @@ Rectangle {
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
@@ -134,7 +134,7 @@ Rectangle {
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
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0

View File

@@ -14,7 +14,7 @@ Rectangle {
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
@@ -139,7 +139,7 @@ Rectangle {
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
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0

View File

@@ -9,7 +9,7 @@ import qs.Widgets
Rectangle {
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
@@ -125,7 +125,7 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2
height: 64
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.width: 0
Column {
@@ -160,7 +160,7 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2
height: 64
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.width: 0
Column {

View File

@@ -206,7 +206,7 @@ Item {
radius: Theme.cornerRadius
color: {
if (modelData.name === currentCodec)
return Theme.surfaceContainerHighest;
return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency);
else if (codecMouseArea.containsMouse)
return Theme.surfaceHover;
else

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
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
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)
@@ -58,8 +85,8 @@ Rectangle {
radius: 18
color: {
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
return Theme.surfaceContainerHigh
return scanMouseArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
return scanMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
}
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
@@ -152,7 +179,7 @@ Rectangle {
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
if (deviceMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
return Theme.surfaceContainerHighest
return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
}
border.color: {
if (modelData.state === BluetoothDeviceState.Connecting)
@@ -327,15 +354,15 @@ Rectangle {
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
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
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
@@ -367,7 +394,7 @@ Rectangle {
StyledText {
text: {
if (modelData.pairing) return "Pairing..."
if (modelData.pairing || isBusy) return "Pairing..."
if (modelData.blocked) return "Blocked"
return BluetoothService.getSignalStrength(modelData)
}
@@ -390,12 +417,12 @@ Rectangle {
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
}
@@ -406,9 +433,7 @@ Rectangle {
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: canConnect && !isBusy
onClicked: {
if (modelData) {
BluetoothService.connectDeviceWithTrust(modelData)
}
root.handlePairDevice(modelData)
}
}
@@ -438,7 +463,7 @@ Rectangle {
property var currentDevice: null
background: Rectangle {
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
@@ -516,10 +541,30 @@ Rectangle {
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,14 +8,89 @@ 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
}
}
const backlight = DisplayService.devices.find(d => d.class === "backlight")
if (backlight) {
return backlight.name
}
const ddc = DisplayService.devices.find(d => d.class === "ddc")
if (ddc) {
return ddc.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.set("brightnessDevicePins", pins)
}
implicitHeight: brightnessContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
@@ -61,112 +136,323 @@ Rectangle {
}
}
Rectangle {
width: parent.width
height: 40
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
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 {
required property var modelData
required property int index
property real deviceBrightness: {
DisplayService.brightnessVersion
return DisplayService.getDeviceBrightness(modelData.name)
}
width: parent.width
height: 80
height: 100
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.name === currentDeviceName ? 2 : 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingM
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
Item {
width: parent.width
height: Math.max(deviceIconColumn.height, deviceInfoColumn.height, exponentControls.height)
DankIcon {
name: {
const deviceClass = modelData.class || ""
const deviceName = modelData.name || ""
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
if (deviceClass === "backlight" || deviceClass === "ddc") {
const brightness = modelData.percentage || 50
if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium"
return "brightness_high"
} else if (deviceName.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
Column {
id: deviceIconColumn
anchors.verticalCenter: parent.verticalCenter
spacing: 2
DankIcon {
name: {
const deviceClass = modelData.class || ""
const deviceName = modelData.name || ""
if (deviceClass === "backlight" || deviceClass === "ddc") {
if (deviceBrightness <= 33)
return "brightness_low"
if (deviceBrightness <= 66)
return "brightness_medium"
return "brightness_high"
} else if (deviceName.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
}
size: Theme.iconSize
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: Math.round(deviceBrightness) + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Column {
id: deviceInfoColumn
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - deviceIconColumn.width - exponentControls.width - Theme.spacingM * 3
StyledText {
text: {
const name = modelData.name || ""
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") {
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
}
return name
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: {
const deviceClass = modelData.class || ""
if (deviceClass === "backlight")
return "Backlight device"
if (deviceClass === "ddc")
return "DDC/CI monitor"
if (deviceClass === "leds")
return "LED device"
return deviceClass
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
}
size: Theme.iconSize
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: (modelData.percentage || 50) + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
Row {
id: exponentControls
width: 140
height: 28
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
visible: SessionData.getBrightnessExponential(modelData.name)
z: 1
StyledRect {
width: 28
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
opacity: SessionData.getBrightnessExponent(modelData.name) > 1.0 ? 1.0 : 0.4
DankIcon {
anchors.centerIn: parent
name: "remove"
size: 14
color: Theme.surfaceText
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
onClicked: {
const current = SessionData.getBrightnessExponent(modelData.name)
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10)
SessionData.setBrightnessExponent(modelData.name, newValue)
}
}
}
StyledRect {
width: 50
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.width: 0
StyledText {
anchors.centerIn: parent
text: SessionData.getBrightnessExponent(modelData.name).toFixed(1)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primary
}
}
StyledRect {
width: 28
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
opacity: SessionData.getBrightnessExponent(modelData.name) < 2.5 ? 1.0 : 0.4
DankIcon {
anchors.centerIn: parent
name: "add"
size: 14
color: Theme.surfaceText
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
onClicked: {
const current = SessionData.getBrightnessExponent(modelData.name)
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10)
SessionData.setBrightnessExponent(modelData.name, newValue)
}
}
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
Rectangle {
width: parent.width
height: 24
radius: height / 2
color: SessionData.getBrightnessExponential(modelData.name) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
StyledText {
text: {
const name = modelData.name || ""
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") {
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
}
return name
Row {
anchors.centerIn: parent
spacing: 4
DankIcon {
name: "show_chart"
size: 14
color: SessionData.getBrightnessExponential(modelData.name) ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: SessionData.getBrightnessExponential(modelData.name) ? "Exponential" : "Linear"
font.pixelSize: Theme.fontSizeSmall
color: SessionData.getBrightnessExponential(modelData.name) ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: {
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") return "Backlight device"
if (deviceClass === "ddc") return "DDC/CI monitor"
if (deviceClass === "leds") return "LED device"
return deviceClass
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const currentState = SessionData.getBrightnessExponential(modelData.name)
SessionData.setBrightnessExponential(modelData.name, !currentState)
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
}
}
MouseArea {
anchors.fill: parent
anchors.bottomMargin: 28
anchors.rightMargin: SessionData.getBrightnessExponential(modelData.name) ? 145 : 0
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (screenName && screenName.length > 0 && modelData.name !== currentDeviceName) {
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
if (pins[screenName]) {
delete pins[screenName]
SettingsData.set("brightnessDevicePins", pins)
}
}
currentDeviceName = modelData.name
deviceNameChanged(modelData.name)
}
}
}
}
}

View File

@@ -15,7 +15,7 @@ Rectangle {
implicitHeight: diskContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
@@ -78,7 +78,7 @@ Rectangle {
width: parent.width
height: 80
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.mount === currentMountPath ? 2 : 0

View File

@@ -17,7 +17,7 @@ Rectangle {
return headerRow.height + wifiOffContent.height + Theme.spacingM
}
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
@@ -34,6 +34,10 @@ Rectangle {
return 1
}
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
return 1
}
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
@@ -76,7 +80,7 @@ Rectangle {
DankButtonGroup {
id: preferenceControls
anchors.verticalCenter: parent.verticalCenter
visible: DMSService.apiVersion >= 5
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex
@@ -196,7 +200,7 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 0 && DMSService.apiVersion >= 5
visible: currentPreferenceIndex === 0 && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
contentHeight: wiredColumn.height
clip: true
@@ -226,7 +230,7 @@ Rectangle {
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
color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.primary
border.width: 0
@@ -306,7 +310,7 @@ Rectangle {
property bool currentConnected: false
background: Rectangle {
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
@@ -372,6 +376,9 @@ Rectangle {
contentHeight: wifiColumn.height
clip: true
property var frozenNetworks: []
property bool menuOpen: false
Column {
id: wifiColumn
width: parent.width
@@ -399,7 +406,7 @@ Rectangle {
}
Repeater {
model: sortedNetworks
model: wifiContent.menuOpen ? wifiContent.frozenNetworks : sortedNetworks
property var sortedNetworks: {
const ssid = NetworkService.currentWifiSSID
@@ -410,6 +417,9 @@ Rectangle {
if (b.ssid === ssid) return 1
return b.signal - a.signal
})
if (!wifiContent.menuOpen) {
wifiContent.frozenNetworks = sorted
}
return sorted
}
delegate: Rectangle {
@@ -419,7 +429,7 @@ Rectangle {
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
color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0
@@ -490,11 +500,13 @@ Rectangle {
if (networkContextMenu.visible) {
networkContextMenu.close()
} else {
wifiContent.menuOpen = true
networkContextMenu.currentSSID = modelData.ssid
networkContextMenu.currentSecured = modelData.secured
networkContextMenu.currentConnected = modelData.ssid === NetworkService.currentWifiSSID
networkContextMenu.currentSaved = modelData.saved
networkContextMenu.currentSignal = modelData.signal
networkContextMenu.currentAutoconnect = modelData.autoconnect || false
networkContextMenu.popup(optionsButton, -networkContextMenu.width + optionsButton.width, optionsButton.height + Theme.spacingXS)
}
}
@@ -537,9 +549,14 @@ Rectangle {
property bool currentConnected: false
property bool currentSaved: false
property int currentSignal: 0
property bool currentAutoconnect: false
onClosed: {
wifiContent.menuOpen = false
}
background: Rectangle {
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
@@ -602,6 +619,29 @@ Rectangle {
}
}
MenuItem {
text: networkContextMenu.currentAutoconnect ? I18n.tr("Disable Autoconnect") : I18n.tr("Enable Autoconnect")
height: (networkContextMenu.currentSaved || networkContextMenu.currentConnected) && DMSService.apiVersion > 13 ? 32 : 0
visible: (networkContextMenu.currentSaved || networkContextMenu.currentConnected) && DMSService.apiVersion > 13
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: {
NetworkService.setWifiAutoconnect(networkContextMenu.currentSSID, !networkContextMenu.currentAutoconnect)
}
}
MenuItem {
text: I18n.tr("Forget Network")
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0

View File

@@ -8,6 +8,7 @@ QtObject {
id: root
property var vpnBuiltinInstance: null
property var cupsBuiltinInstance: null
property var vpnLoader: Loader {
active: false
@@ -32,6 +33,41 @@ QtObject {
}
}
property var cupsLoader: Loader {
active: false
sourceComponent: Component {
CupsWidget {}
}
onItemChanged: {
root.cupsBuiltinInstance = item
if (item && !DMSService.activeSubscriptions.includes("cups") && !DMSService.activeSubscriptions.includes("all")) {
DMSService.addSubscription("cups")
}
}
onActiveChanged: {
if (!active) {
if (DMSService.activeSubscriptions.includes("cups")) {
DMSService.removeSubscription("cups")
}
root.cupsBuiltinInstance = null
}
}
Connections {
target: SettingsData
function onControlCenterWidgetsChanged() {
const widgets = SettingsData.controlCenterWidgets || []
const hasCupsWidget = widgets.some(w => w.id === "builtin_cups")
if (!hasCupsWidget && cupsLoader.active) {
console.log("CupsWidget: No CUPS widget in control center, deactivating loader")
cupsLoader.active = false
}
}
}
}
readonly property var coreWidgetDefinitions: [{
"id": "nightMode",
"text": "Night Mode",
@@ -143,8 +179,17 @@ 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
}, {
"id": "builtin_cups",
"text": "Printers",
"description": "Print Server Management",
"icon": "Print",
"type": "builtin_plugin",
"enabled": CupsService.available,
"warning": !CupsService.available ? "CUPS not available" : undefined,
"isBuiltinPlugin": true
}]

View File

@@ -46,7 +46,7 @@ PanelWindow {
height: 320 // Fixed height to prevent cropping
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)

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

@@ -68,7 +68,7 @@ Row {
unit: "%"
valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: function(newValue) {
if (defaultSink) {
defaultSink.audio.volume = newValue / 100.0

View File

@@ -10,8 +10,10 @@ Row {
property string deviceName: ""
property string instanceId: ""
property string screenName: ""
property var parentScreen: null
signal iconClicked()
signal iconClicked
height: 40
spacing: 0
@@ -21,15 +23,40 @@ 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 : ""
if (found) {
return found.name
}
}
const currentDeviceName = DisplayService.currentDevice
if (currentDeviceName) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceName)
return found ? found.name : ""
if (found) {
return found.name
}
}
const backlight = DisplayService.devices.find(d => d.class === "backlight")
if (backlight) {
return backlight.name
}
const ddc = DisplayService.devices.find(d => d.class === "ddc")
if (ddc) {
return ddc.name
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
@@ -44,6 +71,7 @@ Row {
}
property real targetBrightness: {
DisplayService.brightnessVersion
if (!targetDeviceName) {
return 0
}
@@ -56,9 +84,7 @@ Row {
height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse
? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
: Theme.withAlpha(Theme.primary, 0)
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
MouseArea {
id: iconArea
@@ -76,8 +102,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)
}
}
@@ -97,8 +125,10 @@ Row {
if (targetDevice.class === "backlight" || targetDevice.class === "ddc") {
const brightness = targetBrightness
if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium"
if (brightness <= 33)
return "brightness_low"
if (brightness <= 66)
return "brightness_medium"
return "brightness_high"
} else if (targetDevice.name.includes("kbd")) {
return "keyboard"
@@ -116,16 +146,39 @@ Row {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
enabled: DisplayService.brightnessAvailable && targetDeviceName.length > 0
minimum: 1
maximum: 100
minimum: {
if (!targetDevice) return 1
const isExponential = SessionData.getBrightnessExponential(targetDevice.id)
if (isExponential) {
return 1
}
return (targetDevice.class === "backlight" || targetDevice.class === "ddc") ? 1 : 0
}
maximum: {
if (!targetDevice) return 100
const isExponential = SessionData.getBrightnessExponential(targetDevice.id)
if (isExponential) {
return 100
}
return targetDevice.displayMax || 100
}
value: targetBrightness
onSliderValueChanged: function(newValue) {
showValue: true
unit: {
if (!targetDevice) return "%"
const isExponential = SessionData.getBrightnessExponential(targetDevice.id)
if (isExponential) {
return "%"
}
return targetDevice.class === "ddc" ? "" : "%"
}
onSliderValueChanged: function (newValue) {
if (DisplayService.brightnessAvailable && targetDeviceName) {
DisplayService.setBrightness(newValue, targetDeviceName)
DisplayService.setBrightness(newValue, targetDeviceName, true)
}
}
thumbOutlineColor: Theme.surfaceContainer
trackColor: Theme.surfaceContainerHigh
trackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
}
Loader {
@@ -133,4 +186,4 @@ Row {
active: false
sourceComponent: DankTooltip {}
}
}
}

View File

@@ -20,7 +20,7 @@ Rectangle {
width: parent ? parent.width : 200
height: 60
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
opacity: enabled ? 1.0 : 0.6

View File

@@ -27,7 +27,7 @@ Rectangle {
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
}
readonly property color _containerBg: Theme.surfaceContainerHigh
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
color: {
const baseColor = bodyMouse.containsMouse ? Theme.widgetBaseHoverColor : _containerBg

View File

@@ -66,7 +66,7 @@ Row {
unit: "%"
valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onIsDraggingChanged: {
AudioService.suppressOSD = isDragging
}

View File

@@ -25,7 +25,7 @@ Rectangle {
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText

View File

@@ -27,7 +27,7 @@ Rectangle {
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText

View File

@@ -24,7 +24,7 @@ Rectangle {
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
@@ -42,7 +42,7 @@ Rectangle {
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
}
readonly property color _containerBg: Theme.surfaceContainerHigh
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
Rectangle {
anchors.fill: parent

View File

@@ -21,7 +21,7 @@ function addWidget(widgetId) {
}
widgets.push(widget)
SettingsData.setControlCenterWidgets(widgets)
SettingsData.set("controlCenterWidgets", widgets)
}
function generateUniqueId() {
@@ -32,7 +32,7 @@ function removeWidget(index) {
var widgets = SettingsData.controlCenterWidgets.slice()
if (index >= 0 && index < widgets.length) {
widgets.splice(index, 1)
SettingsData.setControlCenterWidgets(widgets)
SettingsData.set("controlCenterWidgets", widgets)
}
}
@@ -54,12 +54,12 @@ function toggleWidgetSize(index) {
}
}
SettingsData.setControlCenterWidgets(widgets)
SettingsData.set("controlCenterWidgets", widgets)
}
}
function reorderWidgets(newOrder) {
SettingsData.setControlCenterWidgets(newOrder)
SettingsData.set("controlCenterWidgets", newOrder)
}
function moveWidget(fromIndex, toIndex) {
@@ -67,7 +67,7 @@ function moveWidget(fromIndex, toIndex) {
if (fromIndex >= 0 && fromIndex < widgets.length && toIndex >= 0 && toIndex < widgets.length) {
const movedWidget = widgets.splice(fromIndex, 1)[0]
widgets.splice(toIndex, 0, movedWidget)
SettingsData.setControlCenterWidgets(widgets)
SettingsData.set("controlCenterWidgets", widgets)
}
}
@@ -82,9 +82,9 @@ function resetToDefault() {
{"id": "nightMode", "enabled": true, "width": 50},
{"id": "darkMode", "enabled": true, "width": 50}
]
SettingsData.setControlCenterWidgets(defaultWidgets)
SettingsData.set("controlCenterWidgets", defaultWidgets)
}
function clearAll() {
SettingsData.setControlCenterWidgets([])
SettingsData.set("controlCenterWidgets", [])
}

View File

@@ -18,17 +18,7 @@ Item {
anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0)
anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0)
readonly property real dpr: {
if (CompositorService.isNiri && barWindow.screen) {
const niriScale = NiriService.displayScales[barWindow.screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && barWindow.screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return barWindow.screen?.devicePixelRatio || 1
}
readonly property real dpr: CompositorService.getScreenScale(barWindow.screen)
function requestRepaint() {
debounceTimer.restart()
@@ -82,6 +72,12 @@ Item {
function onSurfaceContainerChanged() { root.requestRepaint() }
}
Connections {
target: SettingsData
function onDankBarGothCornerRadiusOverrideChanged() { root.requestRepaint() }
function onDankBarGothCornerRadiusValueChanged() { root.requestRepaint() }
}
onPaint: {
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
@@ -181,6 +177,12 @@ Item {
function onSurfaceChanged() { root.requestRepaint() }
}
Connections {
target: SettingsData
function onDankBarGothCornerRadiusOverrideChanged() { root.requestRepaint() }
function onDankBarGothCornerRadiusValueChanged() { root.requestRepaint() }
}
onPaint: {
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
@@ -288,6 +290,8 @@ Item {
function onDankBarSpacingChanged() { root.requestRepaint() }
function onDankBarSquareCornersChanged() { root.requestRepaint() }
function onDankBarTransparencyChanged() { root.requestRepaint() }
function onDankBarGothCornerRadiusOverrideChanged() { root.requestRepaint() }
function onDankBarGothCornerRadiusValueChanged() { root.requestRepaint() }
}
onPaint: {

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: []
@@ -370,9 +372,6 @@ Item {
}
item.widthChanged.connect(() => layoutTimer.restart())
item.heightChanged.connect(() => layoutTimer.restart())
if (model.widgetId === "spacer") {
item.spacerSize = Qt.binding(() => model.size || 20)
}
if (root.axis && "axis" in item) {
item.axis = Qt.binding(() => root.axis)
}
@@ -396,8 +395,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]

View File

@@ -4,6 +4,7 @@ import QtQuick.Effects
import QtQuick.Shapes
import Quickshell
import Quickshell.Hyprland
import Quickshell.I3
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Services.Notifications
@@ -31,6 +32,9 @@ Item {
focusedScreenName = Hyprland.focusedWorkspace.monitor.name
} else if (CompositorService.isNiri && NiriService.currentOutput) {
focusedScreenName = NiriService.currentOutput
} else if (CompositorService.isSway) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true)
focusedScreenName = focusedWs?.monitor?.name || ""
}
if (!focusedScreenName && barVariants.instances.length > 0) {
@@ -55,6 +59,9 @@ Item {
focusedScreenName = Hyprland.focusedWorkspace.monitor.name
} else if (CompositorService.isNiri && NiriService.currentOutput) {
focusedScreenName = NiriService.currentOutput
} else if (CompositorService.isSway) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true)
focusedScreenName = focusedWs?.monitor?.name || ""
}
if (!focusedScreenName && barVariants.instances.length > 0) {
@@ -110,9 +117,9 @@ Item {
return
}
if (clockButtonRef && dankDashPopoutLoader.item.setTriggerPosition) {
const globalPos = clockButtonRef.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.width)
if (clockButtonRef && clockButtonRef.visualContent && dankDashPopoutLoader.item.setTriggerPosition) {
const globalPos = clockButtonRef.visualContent.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.visualWidth)
const section = clockButtonRef.section || "center"
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen)
} else {
@@ -139,7 +146,7 @@ Item {
}
WlrLayershell.layer: dBarLayer
WlrLayershell.namespace: "quickshell:bar"
WlrLayershell.namespace: "dms:bar"
property var modelData: item
@@ -168,24 +175,12 @@ Item {
readonly property bool isVertical: axis.isVertical
property bool gothCornersEnabled: SettingsData.dankBarGothCornersEnabled
property real wingtipsRadius: Theme.cornerRadius
property real wingtipsRadius: SettingsData.dankBarGothCornerRadiusOverride ? SettingsData.dankBarGothCornerRadiusValue : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property real _backgroundAlpha: topBarCore?.backgroundTransparency ?? SettingsData.dankBarTransparency
readonly property color _bgColor: Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
readonly property real _dpr: {
if (CompositorService.isNiri && barWindow.screen) {
const niriScale = NiriService.displayScales[barWindow.screen.name]
if (niriScale !== undefined)
return niriScale
}
if (CompositorService.isHyprland && barWindow.screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
if (hyprlandMonitor?.scale !== undefined)
return hyprlandMonitor.scale
}
return (barWindow.screen?.devicePixelRatio) || 1
}
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
property string screenName: modelData.name
readonly property int notificationCount: NotificationService.notifications.length
@@ -200,11 +195,6 @@ Item {
property var nativeInhibitor: null
Component.onCompleted: {
const fonts = Qt.fontFamilies()
if (fonts.indexOf("Material Symbols Rounded") === -1) {
ToastService.showError("Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions")
}
if (SettingsData.forceStatusBarLayoutRefresh) {
SettingsData.forceStatusBarLayoutRefresh.connect(() => {
Qt.callLater(() => {
@@ -235,11 +225,11 @@ Item {
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
console.log("DankBar: Plugin loaded:", pluginId)
console.info("DankBar: Plugin loaded:", pluginId)
SettingsData.widgetDataChanged()
}
function onPluginUnloaded(pluginId) {
console.log("DankBar: Plugin unloaded:", pluginId)
console.info("DankBar: Plugin unloaded:", pluginId)
SettingsData.widgetDataChanged()
}
}
@@ -541,6 +531,61 @@ Item {
axis: axis
}
MouseArea {
id: scrollArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
property real scrollAccumulator: 0
property real touchpadThreshold: 500
property bool actionInProgress: false
Timer {
id: cooldownTimer
interval: 100
onTriggered: parent.actionInProgress = false
}
onWheel: wheel => {
if (actionInProgress) {
wheel.accepted = false
return
}
const deltaY = wheel.angleDelta.y
const deltaX = wheel.angleDelta.x
if (CompositorService.isNiri && Math.abs(deltaX) > Math.abs(deltaY)) {
topBarContent.switchApp(deltaX)
wheel.accepted = false
return
}
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
const direction = deltaY < 0 ? 1 : -1
if (isMouseWheel) {
topBarContent.switchWorkspace(direction)
actionInProgress = true
cooldownTimer.restart()
} else {
scrollAccumulator += deltaY
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
const touchDirection = scrollAccumulator < 0 ? 1 : -1
topBarContent.switchWorkspace(touchDirection)
scrollAccumulator = 0
actionInProgress = true
cooldownTimer.restart()
}
}
wheel.accepted = false
}
}
Item {
id: topBarContent
anchors.fill: parent
@@ -556,6 +601,163 @@ Item {
componentMapRevision++
}
readonly property var sortedToplevels: {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, barWindow.screenName);
}
function getRealWorkspaces() {
if (CompositorService.isNiri) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
return NiriService.getCurrentOutputWorkspaceNumbers()
}
const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName).map(ws => ws.idx + 1)
return workspaces.length > 0 ? workspaces : [1, 2]
} else if (CompositorService.isHyprland) {
const workspaces = Hyprland.workspaces?.values || []
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
const sorted = workspaces.slice().sort((a, b) => a.id - b.id)
const filtered = sorted.filter(ws => ws.id > -1)
return filtered.length > 0 ? filtered : [{"id": 1, "name": "1"}]
}
const monitorWorkspaces = workspaces.filter(ws => {
return ws.lastIpcObject && ws.lastIpcObject.monitor === barWindow.screenName && ws.id > -1
})
if (monitorWorkspaces.length === 0) {
return [{"id": 1, "name": "1"}]
}
return monitorWorkspaces.sort((a, b) => a.id - b.id)
} else if (CompositorService.isDwl) {
if (!DwlService.dwlAvailable) {
return [0]
}
if (SettingsData.dwlShowAllTags) {
return Array.from({length: DwlService.tagCount}, (_, i) => i)
}
return DwlService.getVisibleTags(barWindow.screenName)
} else if (CompositorService.isSway) {
const workspaces = I3.workspaces?.values || []
if (workspaces.length === 0) return [{"num": 1}]
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
return workspaces.slice().sort((a, b) => a.num - b.num)
}
const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === barWindow.screenName)
return monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.num - b.num) : [{"num": 1}]
}
return [1]
}
function getCurrentWorkspace() {
if (CompositorService.isNiri) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
return NiriService.getCurrentWorkspaceNumber()
}
const activeWs = NiriService.allWorkspaces.find(ws => ws.output === barWindow.screenName && ws.is_active)
return activeWs ? activeWs.idx + 1 : 1
} else if (CompositorService.isHyprland) {
const monitors = Hyprland.monitors?.values || []
const currentMonitor = monitors.find(monitor => monitor.name === barWindow.screenName)
return currentMonitor?.activeWorkspace?.id ?? 1
} else if (CompositorService.isDwl) {
if (!DwlService.dwlAvailable) return 0
const outputState = DwlService.getOutputState(barWindow.screenName)
if (!outputState || !outputState.tags) return 0
const activeTags = DwlService.getActiveTags(barWindow.screenName)
return activeTags.length > 0 ? activeTags[0] : 0
} else if (CompositorService.isSway) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true)
return focusedWs ? focusedWs.num : 1
}
const focusedWs = I3.workspaces?.values?.find(ws => ws.monitor?.name === barWindow.screenName && ws.focused === true)
return focusedWs ? focusedWs.num : 1
}
return 1
}
function switchWorkspace(direction) {
const realWorkspaces = getRealWorkspaces()
if (realWorkspaces.length < 2) {
return
}
if (CompositorService.isNiri) {
const currentWs = getCurrentWorkspace()
const currentIndex = realWorkspaces.findIndex(ws => ws === currentWs)
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) {
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1)
}
} else if (CompositorService.isHyprland) {
const currentWs = getCurrentWorkspace()
const currentIndex = realWorkspaces.findIndex(ws => ws.id === currentWs)
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) {
Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`)
}
} else if (CompositorService.isDwl) {
const currentTag = getCurrentWorkspace()
const currentIndex = realWorkspaces.findIndex(tag => tag === currentTag)
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) {
DwlService.switchToTag(barWindow.screenName, realWorkspaces[nextIndex])
}
} else if (CompositorService.isSway) {
const currentWs = getCurrentWorkspace()
const currentIndex = realWorkspaces.findIndex(ws => ws.num === currentWs)
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) {
try { I3.dispatch(`workspace number ${realWorkspaces[nextIndex].num}`) } catch(_){}
}
}
}
function switchApp(deltaY) {
const windows = 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();
}
}
readonly property int availableWidth: width
readonly property int launcherButtonWidth: 40
readonly property int workspaceSwitcherWidth: 120
@@ -585,18 +787,18 @@ Item {
function getWidgetSection(parentItem) {
let current = parentItem
while (current) {
if (current.objectName === "leftSection" || current === hLeftSection || current === vLeftSection) {
if (current.objectName === "leftSection") {
return "left"
}
if (current.objectName === "centerSection" || current === hCenterSection || current === vCenterSection) {
if (current.objectName === "centerSection") {
return "center"
}
if (current.objectName === "rightSection" || current === hRightSection || current === vRightSection) {
if (current.objectName === "rightSection") {
return "right"
}
current = current.parent
}
return "left" // fallback
return "left"
}
readonly property var widgetVisibility: ({
@@ -695,6 +897,9 @@ Item {
LeftSection {
id: hLeftSection
objectName: "leftSection"
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
@@ -710,6 +915,9 @@ Item {
RightSection {
id: hRightSection
objectName: "rightSection"
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
@@ -725,6 +933,9 @@ Item {
CenterSection {
id: hCenterSection
objectName: "centerSection"
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
@@ -746,6 +957,9 @@ Item {
LeftSection {
id: vLeftSection
objectName: "leftSection"
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
anchors {
top: parent.top
@@ -762,6 +976,9 @@ Item {
CenterSection {
id: vCenterSection
objectName: "centerSection"
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
anchors {
verticalCenter: parent.verticalCenter
@@ -778,6 +995,9 @@ Item {
RightSection {
id: vRightSection
objectName: "rightSection"
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
height: implicitHeight
anchors {
@@ -814,6 +1034,7 @@ Item {
id: launcherButtonComponent
LauncherButton {
id: launcherButton
isActive: false
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
@@ -823,6 +1044,12 @@ Item {
hyprlandOverviewLoader: root.hyprlandOverviewLoader
onClicked: {
appDrawerLoader.active = true
if (appDrawerLoader.item && appDrawerLoader.item.setTriggerPosition) {
const globalPos = launcherButton.visualContent.mapToGlobal(0, 0)
const currentScreen = barWindow.screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, launcherButton.visualWidth)
appDrawerLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, launcherButton.section, currentScreen)
}
appDrawerLoader.item?.toggle()
}
}
@@ -1214,13 +1441,19 @@ Item {
Component {
id: separatorComponent
Rectangle {
width: barWindow.isVertical ? barWindow.widgetThickness * 0.67 : 1
height: barWindow.isVertical ? 1 : barWindow.widgetThickness * 0.67
Item {
width: barWindow.isVertical ? parent.barThickness : 1
height: barWindow.isVertical ? 1 : parent.barThickness
implicitWidth: width
implicitHeight: height
color: Theme.outline
opacity: 0.3
Rectangle {
width: barWindow.isVertical ? parent.width * 0.6 : 1
height: barWindow.isVertical ? 1 : parent.height * 0.6
anchors.centerIn: parent
color: Theme.outline
opacity: 0.3
}
}
}

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

@@ -11,6 +11,8 @@ import qs.Widgets
DankPopout {
id: root
layerNamespace: "dms:battery"
property var triggerScreen: null
function setTriggerPosition(x, y, width, section, screen) {
@@ -56,7 +58,7 @@ DankPopout {
id: batteryContent
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Theme.outlineMedium
border.width: 0
@@ -235,7 +237,7 @@ DankPopout {
}
StyledText {
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
text: BatteryService.batteryStatus
font.pixelSize: Theme.fontSizeLarge
color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
@@ -247,6 +249,7 @@ DankPopout {
return Theme.surfaceText;
}
font.weight: Font.Medium
visible: BatteryService.batteryAvailable
anchors.verticalCenter: parent.verticalCenter
}
}
@@ -303,7 +306,7 @@ DankPopout {
width: (parent.width - Theme.spacingM) / 2
height: 64
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
@@ -338,7 +341,7 @@ DankPopout {
width: (parent.width - Theme.spacingM) / 2
height: 64
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
@@ -387,7 +390,7 @@ DankPopout {
width: parent.width
height: batteryColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.width: 0
Column {
@@ -457,7 +460,7 @@ DankPopout {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
@@ -494,7 +497,7 @@ DankPopout {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
@@ -523,7 +526,7 @@ DankPopout {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {

View File

@@ -13,8 +13,19 @@ import qs.Widgets
DankPopout {
id: root
layerNamespace: "dms:vpn"
Ref {
service: VpnService
service: DMSNetworkService
}
property bool wasVisible: false
onShouldBeVisibleChanged: {
if (shouldBeVisible && !wasVisible) {
DMSNetworkService.getState()
}
wasVisible = shouldBeVisible
}
property var triggerScreen: null
@@ -42,7 +53,7 @@ DankPopout {
id: content
implicitHeight: contentColumn.height + Theme.spacingL * 2
color: Theme.popupBackground()
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Theme.outlineMedium
border.width: 0
@@ -143,7 +154,7 @@ DankPopout {
width: parent.width
implicitHeight: detailsColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, Theme.getContentBackgroundAlpha() * 0.6)
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineStrong
border.width: 0
clip: true
@@ -161,11 +172,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");
}
@@ -175,11 +186,10 @@ DankPopout {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Item {
elide: Text.ElideRight
wrapMode: Text.NoWrap
Layout.fillWidth: true
height: 1
Layout.maximumWidth: parent.width - 140
}
// Removed Quick Connect for clarity
@@ -193,11 +203,12 @@ 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
border.color: Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
Row {
anchors.centerIn: parent
@@ -223,8 +234,9 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.disconnectAllActive()
}
}
@@ -251,7 +263,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 +296,7 @@ DankPopout {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -292,9 +304,10 @@ 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
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
RowLayout {
anchors.left: parent.left
@@ -304,20 +317,24 @@ 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
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
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
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
@@ -391,8 +408,9 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
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

@@ -50,7 +50,6 @@ BasePill {
StyledText {
text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.barTextSize(battery.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
@@ -87,7 +86,6 @@ BasePill {
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.barTextSize(battery.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
@@ -97,8 +95,10 @@ BasePill {
}
MouseArea {
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: {
@@ -108,7 +108,7 @@ BasePill {
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, battery.visualWidth)
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
toggleBatteryPopup();
toggleBatteryPopup()
}
}
}

View File

@@ -37,9 +37,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -54,9 +54,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -68,18 +68,18 @@ BasePill {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
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
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -92,18 +92,18 @@ BasePill {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
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
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -134,9 +134,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -149,9 +149,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -169,9 +169,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -184,9 +184,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
}
@@ -198,28 +198,30 @@ BasePill {
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.verticalCenter: parent.verticalCenter
anchors.baseline: dateText.baseline
}
StyledText {
id: middleDot
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
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)
@@ -237,15 +239,16 @@ BasePill {
}
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 (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
const globalPos = root.visualContent.mapToGlobal(0, 0)
const currentScreen = root.parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, width)
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

@@ -27,7 +27,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.colorPickerRequested()

View File

@@ -31,10 +31,15 @@ BasePill {
return "sync"
}
if (NetworkService.networkStatus === "ethernet") {
const status = NetworkService.networkStatus
if (status === "ethernet") {
return "lan"
}
if (status === "vpn") {
return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon
}
return NetworkService.wifiSignalIcon
}
size: Theme.barIconSize(root.barThickness)
@@ -86,7 +91,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
@@ -126,22 +130,27 @@ BasePill {
name: {
if (NetworkService.wifiToggling) {
return "sync";
return "sync"
}
if (NetworkService.networkStatus === "ethernet") {
return "lan";
const status = NetworkService.networkStatus
if (status === "ethernet") {
return "lan"
}
return NetworkService.wifiSignalIcon;
if (status === "vpn") {
return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon
}
return NetworkService.wifiSignalIcon
}
size: Theme.barIconSize(root.barThickness)
color: {
if (NetworkService.wifiToggling) {
return Theme.primary;
return Theme.primary
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
}
anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
@@ -188,7 +197,6 @@ BasePill {
id: audioWheelArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
@@ -228,8 +236,10 @@ BasePill {
}
MouseArea {
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: {

View File

@@ -59,7 +59,6 @@ BasePill {
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -59,7 +59,6 @@ BasePill {
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100°"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

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