122 Commits

Author SHA1 Message Date
Inorishio 8c48ddbbe7 Merge branch 'main' of git.zach-dev.cc:zach/z-bar-qt 2026-05-21 23:58:49 +02:00
Inorishio 625d766719 added escape to lock + greeter 2026-05-21 23:58:24 +02:00
zach 88526b9e98 show notif icon true by default 2026-05-21 23:43:27 +02:00
zach a0d56b965c toggle to show notif icon on lockscreen 2026-05-21 23:29:49 +02:00
zach 2342edcf66 apply button, wheel doesn't zoom 2026-05-21 23:12:01 +02:00
zach 9e75b593f4 scale slider for crop tool 2026-05-21 19:14:29 +02:00
zach 4663c7d683 better wallpaper preview positioning 2026-05-21 17:55:31 +02:00
zach 80683800eb crop region now restores correctly 2026-05-21 16:52:12 +02:00
zach 57836f974c remove residue test files 2026-05-21 15:52:43 +02:00
zach 8dbb88e136 crop region now correct 2026-05-21 15:51:42 +02:00
zach 06c402c050 better crop region handling, but coordinate math is wrong 2026-05-20 23:16:55 +02:00
zach e425a1701b small fix for wallpaper grid 2026-05-20 14:34:11 +02:00
zach 41666d0150 Merge pull request 'hyprland lua support' (#91) from hypr-plugin into main
Reviewed-on: #91
2026-05-20 14:08:30 +02:00
zach 853b683962 Changed base deform numbers, less bouncy
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Lint & Format (Python) / lint-format (pull_request) Successful in 24s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-05-20 07:45:18 +02:00
zach b1bfcb3ed0 Merge branch 'main' into hypr-plugin
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Lint & Format (Python) / lint-format (pull_request) Successful in 19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-05-20 07:35:26 +02:00
zach 68662120ba Merge pull request 'Lint/formatter workflows merged per language. Resolved errors and warnings for each workflow' (#90) from 89-lint-format-fixes into main
Reviewed-on: #90
2026-05-20 07:33:56 +02:00
zach b8af60008d fixed applying hyprland options and rules, as well as fetching
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 24s
Lint (Rust) / lint (pull_request) Failing after 1m37s
2026-05-20 07:30:38 +02:00
zach b8524ff621 experimental hyprland lua support 2026-05-20 07:17:38 +02:00
zach ffde4063a0 experimental hyprland lua support 2026-05-20 07:13:05 +02:00
zach 96bf5f3365 cleanup for hyprland lua configs 2026-05-20 06:50:14 +02:00
zach 053efb4aaf reintroduce cargo caching
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-05-20 05:23:56 +02:00
AramJonghu c88aef2164 removal of cache (for now)
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m33s
2026-05-20 01:03:51 +02:00
AramJonghu 01b54ec5e1 use of newest node version available + attempt cache v3 vs v4
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Lint & Format (Python) / lint-format (pull_request) Successful in 25s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m31s
2026-05-20 00:59:41 +02:00
AramJonghu 7276ee28dc minor updates to rust workflow
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m33s
2026-05-20 00:46:59 +02:00
AramJonghu a14ebe2016 wrong cache url
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 17s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m30s
2026-05-20 00:26:50 +02:00
AramJonghu d3f6765819 changed github specific caching to forgejo's solution
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Lint & Format (Python) / lint-format (pull_request) Successful in 18s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m31s
2026-05-20 00:19:46 +02:00
AramJonghu a3d0ee18cb added cargo to be cached to avoid recompiling every time. Also added echo messages for more info
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Lint & Format (Python) / lint-format (pull_request) Successful in 19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m35s
2026-05-20 00:11:20 +02:00
AramJonghu c9d6b95ca5 main.rs formatted
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
Lint & Format (Python) / lint-format (pull_request) Successful in 26s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m36s
2026-05-20 00:06:30 +02:00
AramJonghu 794a26a3fe eslint workflow now prints a succes or fail message eslint is run succesfully or if it ran but failed
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 18s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-20 00:05:25 +02:00
AramJonghu ca3a288eab minor changes to workflows to prevent preemptive exits/failures
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 21s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m34s
2026-05-20 00:02:14 +02:00
AramJonghu 902863e5ba adjusted workflows -> merge of lint/formatter per lang
Lint & Format (JS/TS) / lint-format (pull_request) Failing after 10s
Lint & Format (Python) / lint-format (pull_request) Failing after 19s
Lint & Format (Rust) / lint-format (pull_request) Failing after 33s
2026-05-19 23:57:11 +02:00
AramJonghu dd49198cf7 prettier and eslint ignore valid syntax for qml specific syntax. Clippy lint resolved in .rs. unused py files commented to be ignored by ruff 2026-05-19 23:53:09 +02:00
AramJonghu ceca949535 Merge pull request 'fix blobs dirty tracking' (#87) from blob-testing into main
Reviewed-on: #87
Reviewed-by: AramJonghu <2+aramjonghu@noreply.git.zach-dev.cc>
2026-05-19 23:18:57 +02:00
zach 24d5584b98 wallpaper now uses Image (hopefully temporarily)
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m36s
2026-05-19 22:12:37 +02:00
zach 62ec1b9f33 cleanup unneeded logging
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m34s
2026-05-19 16:15:39 +02:00
zach 9c6a1ce1a4 hide notification content on lockscreen, toggleable
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 18s
Lint (Rust) / lint (pull_request) Failing after 1m32s
2026-05-19 16:10:00 +02:00
zach b6ad180b6a select part of wallpaper to show
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 12s
Lint (Python) / lint (pull_request) Failing after 20s
Lint (Rust) / lint (pull_request) Failing after 1m40s
2026-05-19 15:38:59 +02:00
zach b20767c702 hopefully increase drawing performance
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 18s
Lint (Rust) / lint (pull_request) Failing after 1m32s
2026-05-19 10:04:04 +02:00
zach 362b7bb8c2 add the drawing popout background
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m36s
2026-05-19 08:43:52 +02:00
zach 405825518a move search index file to correct place
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m30s
2026-05-19 08:30:31 +02:00
zach db7a822caf fix some backgrounds, now attaching to wrappers rather than panel itself most of the time
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m35s
2026-05-19 08:24:05 +02:00
zach 3bd9444e2f fix settings background desync
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 25s
Lint (Rust) / lint (pull_request) Failing after 1m33s
2026-05-19 07:09:39 +02:00
zach d0e696c681 update blobs
Format (JS/TS) / format (pull_request) Failing after 8s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 29s
Lint (Rust) / lint (pull_request) Failing after 1m34s
2026-05-19 04:52:28 +02:00
zach 550630feaa Revert "update blobs"
This reverts commit 8c22855dd8.
2026-05-19 04:24:24 +02:00
zach 8c22855dd8 update blobs 2026-05-19 04:20:35 +02:00
zach 015ee61885 revert blobs 2026-05-19 04:06:36 +02:00
zach 8fba953f52 update blobs 2026-05-19 04:04:32 +02:00
zach 3d2fc0a3b1 update blobs 2026-05-19 04:02:46 +02:00
zach c060be79e8 update blobs 2026-05-19 03:56:56 +02:00
zach cb1df5078b revert blobs 2026-05-19 03:55:48 +02:00
zach 5eb32fc30c update blobs 2026-05-19 03:53:47 +02:00
zach 0a84c822d5 update blobs 2026-05-19 03:50:24 +02:00
zach f89236f51e update blobs 2026-05-19 03:43:08 +02:00
zach 4b1316e887 log exclusion zones sizing, debugging inconsistent border exclusions 2026-05-18 18:25:23 +02:00
zach 63f4694322 rounding scale applies to more rounded elements, slight rounding changes to tray menu items 2026-05-17 00:52:53 +02:00
zach 51a8f1d5e1 remove residue file 2026-05-17 00:09:08 +02:00
zach d57010a501 Merge pull request 'prettierrc line update to 80 from 100, and singleQuote -> false from true' (#86) from forgejo-workflows into main
Reviewed-on: #86
2026-05-16 01:46:27 +02:00
AramJonghu d506d5ad27 not fmt, clippy linter
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 13s
Lint (Python) / lint (pull_request) Failing after 20s
Lint (Rust) / lint (pull_request) Failing after 1m40s
2026-05-16 01:42:30 +02:00
AramJonghu f6119072f7 rustfmt added
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 24s
Lint (Rust) / lint (pull_request) Failing after 35s
2026-05-16 01:40:08 +02:00
AramJonghu 00063309cd cargo not installed yet in yml
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 26s
Lint (Rust) / lint (pull_request) Failing after 36s
2026-05-16 01:38:57 +02:00
AramJonghu c30891de83 rustfmt w/o project
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 17s
Lint (Rust) / lint (pull_request) Failing after 38s
2026-05-16 01:37:38 +02:00
AramJonghu 55e9c0f267 rustfmt w/o project
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 21s
Lint (Rust) / lint (pull_request) Failing after 35s
2026-05-16 01:35:54 +02:00
AramJonghu 3424df53e2 Merge branch 'main' into forgejo-workflows
Format (JS/TS) / format (pull_request) Failing after 12s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 31s
Lint (Rust) / lint (pull_request) Successful in 48s
2026-05-16 01:33:03 +02:00
AramJonghu 583c50f994 prettierrc line update to 80 from 100, and singleQuote -> false from true
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 12s
Lint (Python) / lint (pull_request) Failing after 20s
Lint (Rust) / lint (pull_request) Successful in 59s
2026-05-16 01:32:25 +02:00
zach ca53152630 Merge pull request 'testing ci using external forgejo runner' (#84) from forgejo-workflows into main
Reviewed-on: #84
2026-05-16 01:26:00 +02:00
zach 0cd7df243b Merge pull request 'Screenshot tool' (#83) from screenshot-tool into main
Reviewed-on: #83
2026-05-16 01:24:48 +02:00
AramJonghu 6a8ad4dbf2 removal qml linter, not worth
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 27s
Lint (Rust) / lint (pull_request) Successful in 29s
2026-05-16 01:15:42 +02:00
AramJonghu f57577fefd aur to arch
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 35s
Lint (Python) / lint (pull_request) Failing after 45s
Lint (Rust) / lint (pull_request) Successful in 1m13s
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 01:11:17 +02:00
AramJonghu 3a05cd339d qml to arch
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 18s
Lint (Python) / lint (pull_request) Failing after 18s
Lint (Rust) / lint (pull_request) Successful in 35s
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 01:03:26 +02:00
AramJonghu c67a498f8d qml path issue fixed
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 25s
Lint (Rust) / lint (pull_request) Successful in 53s
Lint (QML) / lint (pull_request) Failing after 2m2s
2026-05-16 00:53:49 +02:00
AramJonghu e7e772ebc6 qml needs node
Format (JS/TS) / format (pull_request) Failing after 9s
Lint (JS/TS) / lint (pull_request) Failing after 20s
Lint (Python) / lint (pull_request) Failing after 27s
Lint (Rust) / lint (pull_request) Successful in 55s
Lint (QML) / lint (pull_request) Successful in 2m3s
2026-05-16 00:50:26 +02:00
AramJonghu fb2c9c6a21 qml goes to debian:sid for simplicity
Format (JS/TS) / format (pull_request) Failing after 10s
Lint (JS/TS) / lint (pull_request) Failing after 17s
Lint (Python) / lint (pull_request) Failing after 25s
Lint (QML) / lint (pull_request) Failing after 37s
Lint (Rust) / lint (pull_request) Successful in 42s
2026-05-16 00:43:03 +02:00
AramJonghu 383671344f qml goes to Ubuntu for simplicity
Format (JS/TS) / format (pull_request) Failing after 9s
Lint (JS/TS) / lint (pull_request) Failing after 18s
Lint (Python) / lint (pull_request) Failing after 22s
Lint (Rust) / lint (pull_request) Successful in 34s
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 00:41:11 +02:00
AramJonghu beb1d96750 added eslint config
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 9s
Lint (Python) / lint (pull_request) Failing after 15s
Lint (QML) / lint (pull_request) Failing after 1m18s
Lint (Rust) / lint (pull_request) Successful in 30s
2026-05-16 00:37:20 +02:00
AramJonghu 6f8af9028b added eslint config
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 8s
Lint (Python) / lint (pull_request) Failing after 14s
Lint (QML) / lint (pull_request) Failing after 1m19s
Lint (Rust) / lint (pull_request) Has been cancelled
2026-05-16 00:35:18 +02:00
AramJonghu e874c19ee2 now should report issue when incorrect
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Successful in 5s
Lint (Python) / lint (pull_request) Failing after 14s
Lint (Rust) / lint (pull_request) Has been cancelled
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 00:33:37 +02:00
AramJonghu af04e5d227 case sensitivity
Format (JS/TS) / format (pull_request) Successful in 7s
Lint (JS/TS) / lint (pull_request) Successful in 5s
Lint (Python) / lint (pull_request) Successful in 14s
Lint (QML) / lint (pull_request) Successful in 1m20s
Lint (Rust) / lint (pull_request) Has been cancelled
2026-05-16 00:31:35 +02:00
AramJonghu 4ab19a8e37 separate all workflows, better overview
Format (JS/TS) / format (pull_request) Successful in 6s
Lint (JS/TS) / lint (pull_request) Successful in 5s
Lint (Python) / lint (pull_request) Successful in 14s
Lint (QML) / lint (pull_request) Successful in 1m19s
Lint (Rust) / lint (pull_request) Has been cancelled
2026-05-16 00:29:31 +02:00
AramJonghu 783d05f815 test different repo apline
CI (Lint + Format Checks) / lint (pull_request) Successful in 1m48s
2026-05-16 00:25:32 +02:00
AramJonghu 17fb9c0fef apline-edge for qmllint
CI (Lint + Format Checks) / lint (pull_request) Successful in 41s
2026-05-16 00:24:06 +02:00
AramJonghu 39cbfa2c93 minor adjustments and added .prettierrc.json
CI (Lint + Format Checks) / lint (pull_request) Successful in 1m41s
2026-05-16 00:16:17 +02:00
Inorishio 5c5018033d Merge branch 'screenshot-tool' of git.zach-dev.cc:zach/z-bar-qt into screenshot-tool 2026-05-16 00:15:10 +02:00
Inorishio d9afc6c7c7 Lockscreen buttons, HoverIconButton component added 2026-05-16 00:14:58 +02:00
AramJonghu 17fef78672 fix python venv in ci
CI (Lint + Format Checks) / lint (pull_request) Failing after 38s
2026-05-16 00:06:20 +02:00
AramJonghu c120dcae41 added node as this is required
CI (Lint + Format Checks) / lint (pull_request) Failing after 41s
2026-05-16 00:04:50 +02:00
AramJonghu 64e65ca9df test
CI (Lint + Format Checks) / lint (pull_request) Failing after 4s
2026-05-16 00:01:14 +02:00
AramJonghu 22a7993c07 test
CI (Lint + Format Checks) / lint (pull_request) Failing after 2s
2026-05-15 23:59:39 +02:00
AramJonghu 24526ca2d1 runs on Linux, not alpine?
CI (Lint + Format Checks) / lint (pull_request) Has been cancelled
2026-05-15 23:56:30 +02:00
AramJonghu c5ee27bf62 testing ci using external forgejo runner
CI (Lint + Format Checks) / lint (pull_request) Failing after 7s
2026-05-15 23:52:46 +02:00
zach 7cec08d262 update category changes 2026-05-15 23:42:10 +02:00
Inorishio 97b657ce9a dpms on and off fix no need to change configs 2026-05-15 19:02:48 +02:00
zach 33f6706658 update category changes 2026-05-15 18:59:04 +02:00
zach f6c4dc8ee1 experimental update category in settings 2026-05-14 01:47:04 +02:00
zach a53a4b32eb fix shadow blur cut off in screenshots 2026-05-13 19:43:34 +02:00
zach e80ac202d0 reordering slightly, setting up for pkgbuild 2026-05-13 18:00:32 +02:00
Inorishio 6e6f6c28f6 effects tool in /plugins, bin in /scripts, edited picker.qml to work with installed bin in /usr/bin 2026-05-13 17:35:19 +02:00
Inorishio 26bfa952d7 Screenshot settings + ss search 2026-05-12 22:56:46 +02:00
Inorishio 611abdf028 Rounded corners + shadow 2026-05-12 17:57:06 +02:00
Inorishio 37a112a04b Breaking zshell: toggle-launcher globalshortcut fix 2026-05-11 19:41:56 +02:00
zach bfc09c71a8 wifi fix hopefully 2026-05-09 00:46:46 +02:00
zach c7fabf9fc5 wifi fix hopefully 2026-05-09 00:40:06 +02:00
zach 0bae21c891 wifi fix hopefully 2026-05-09 00:36:43 +02:00
zach 53733c7fe0 icon fill anim 2026-05-09 00:11:32 +02:00
zach 836b92cc5f Merge pull request 'Bar entries rework in settings' (#80) from settings-bar-entries-rework into main
Reviewed-on: #80
2026-05-08 19:31:37 +02:00
zach bcc75abc54 labels instead of icons for bar entries 2026-05-08 19:27:36 +02:00
zach 11c185baa6 ipc call for launcher 2026-05-07 23:22:53 +02:00
zach 7cc056c327 ipc call for launcher 2026-05-07 23:19:26 +02:00
zach f657741551 fix for wifi toggle 2026-05-05 23:17:40 +02:00
zach 073e1dd8b1 primitive game mode toggle for Hyprland 2026-05-05 19:04:01 +02:00
zach 8fa447c63d end of rework, uses horizontal icon buttons instead of drag handlers and vertical views 2026-05-05 12:14:06 +02:00
zach e4113994dc start of rework 2026-05-03 18:09:55 +02:00
zach 8a2eeb6c31 add support for custom date formats via config 2026-05-03 17:30:10 +02:00
zach 10340a83dd notif open and exit anim fixed 2026-05-02 19:27:06 +02:00
zach c2bd45db4a updates popout delegate creation buffer 2026-04-30 23:51:10 +02:00
zach d5256a3952 Merge pull request 'Nix flake not up to date update' (#77) from nixos-readme into main
Reviewed-on: #77
2026-04-29 21:32:56 +02:00
AramJonghu abd85388f6 removal of binary file 2026-04-29 20:32:20 +02:00
AramJonghu 2bad732592 Nix flake not up to date update 2026-04-29 20:30:58 +02:00
zach 47120db391 gitignore 2026-04-29 20:22:00 +02:00
zach d568cd34ab double clicked 2026-04-29 20:19:22 +02:00
zach a3a55ba8d1 double clicked 2026-04-29 20:16:03 +02:00
zach 8db28ec3a0 oops 2026-04-29 20:01:02 +02:00
zach 9688e99a8e Merge pull request 'changes that should be in main' (#76) from 73-colorscheme-options into main
Reviewed-on: #76
2026-04-29 19:59:12 +02:00
104 changed files with 5295 additions and 2662 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Lint & Format (JS/TS)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git
- name: Prettier
continue-on-error: true
run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
npx --yes prettier --check "**/*.{js,jsx,ts,tsx,mjs,cjs}" --ignore-path .prettierignore
else
echo "No JS/TS files found"
fi
- name: ESLint
run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
if [ -f package.json ]; then
npm install --no-audit --no-fund
fi
if [ -f eslint.config.js ] || [ -f eslint.config.mjs ] || [ -f eslint.config.cjs ] || [ -f .eslintrc ] || [ -f .eslintrc.js ] || [ -f .eslintrc.cjs ] || [ -f .eslintrc.json ] || [ -f .eslintrc.yaml ] || [ -f .eslintrc.yml ]; then
npx --yes eslint . && echo "ESLint passed" || echo "ESLint failed"
else
echo "No eslint config found"
fi
else
echo "No JS/TS files found"
fi
+34
View File
@@ -0,0 +1,34 @@
name: Lint & Format (Python)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Format check
continue-on-error: true
run: |
. .venv/bin/activate
ruff format --check .
- name: Lint
run: |
. .venv/bin/activate
ruff check .
+72
View File
@@ -0,0 +1,72 @@
name: Lint & Format (Rust)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache cargo packages
uses: actions/cache@v4
env:
cache-name: cache-cargo-packages
with:
path: |
.cargo/registry
.cargo/git
target
key: rust-${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Install tools
run: |
apk add --no-cache \
git \
cargo \
rust \
rustfmt \
rust-clippy
- name: Format check
continue-on-error: true
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \
echo "$manifest: needs formatting"
done
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
- name: Clippy
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo clippy --manifest-path "$manifest" --all-targets --all-features -- -D warnings && \
echo "$manifest: Clippy passed" || \
{ echo "$manifest: Clippy failed"; status=1; }
done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
+2
View File
@@ -1,3 +1,4 @@
**/__pycache__/
./result/
.pyre/
.cache/
@@ -11,3 +12,4 @@ pkg/
uv.lock
.qtcreator/
dist/
**/target/
+3
View File
@@ -0,0 +1,3 @@
.venv/
scripts/fzf.js
scripts/fuzzysort.js
+13
View File
@@ -0,0 +1,13 @@
{
"semi": true,
"singleQuote": false,
"jsxSingleQuote": false,
"tabWidth": 4,
"printWidth": 80,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf",
"proseWrap": "preserve"
}
+2 -2
View File
@@ -2,7 +2,7 @@ import QtQuick
import qs.Config
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
duration: Appearance.anim.durations.normal
easing.bezierCurve: Appearance.anim.curves.standard
easing.type: Easing.BezierSpline
}
+33
View File
@@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import qs.Config
IconButton {
id: root
required property bool shouldBeVisible
opacity: 0
scale: 0
visible: root.scale > 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on scale {
Anim {
}
}
onShouldBeVisibleChanged: {
if (root.shouldBeVisible) {
root.opacity = 1;
root.scale = 1;
} else {
root.opacity = 0;
root.scale = 0;
}
}
}
+1 -2
View File
@@ -41,12 +41,11 @@ CustomRect {
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitHeight: label.implicitHeight + padding * 2
implicitWidth: implicitHeight
radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1)
radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale
Behavior on radius {
Anim {
id: radiusAnim
}
}
+4
View File
@@ -7,4 +7,8 @@ JsonObject {
property real alignX: 0.5
property real alignY: 0.5
property real zoom: 1.0
property real sourceClipX: 0
property real sourceClipY: 0
property real sourceClipW: 0
property real sourceClipH: 0
}
+1 -12
View File
@@ -8,10 +8,6 @@ JsonObject {
id: "workspaces",
enabled: true
},
{
id: "audio",
enabled: true
},
{
id: "media",
enabled: true
@@ -24,10 +20,6 @@ JsonObject {
id: "updates",
enabled: true
},
{
id: "dash",
enabled: true
},
{
id: "spacer",
enabled: true
@@ -48,10 +40,6 @@ JsonObject {
id: "tray",
enabled: true
},
{
id: "upower",
enabled: false
},
{
id: "network",
enabled: false
@@ -70,6 +58,7 @@ JsonObject {
property Popouts popouts: Popouts {
}
property int rounding: 8
property int smoothing: 32
component Popouts: JsonObject {
property bool activeWindow: true
+27 -1
View File
@@ -23,6 +23,7 @@ Singleton {
property alias osd: adapter.osd
property alias overview: adapter.overview
property bool recentlySaved: false
property alias screenshot: adapter.screenshot
property alias services: adapter.services
property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities
@@ -82,6 +83,10 @@ Singleton {
wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled,
alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY,
zoom: background.zoom
};
@@ -93,6 +98,7 @@ Singleton {
hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding,
border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height,
popouts: {
tray: barConfig.popouts.tray,
@@ -128,7 +134,8 @@ Singleton {
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors(),
dock: serializeDock()
dock: serializeDock(),
screenshot: serializeScreenshot()
};
}
@@ -180,6 +187,7 @@ Singleton {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
color: {
mode: general.color.mode,
smart: general.color.smart,
@@ -233,6 +241,8 @@ Singleton {
return {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
showNotifIcon: lock.showNotifIcon,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
sizes: {
@@ -274,6 +284,20 @@ Singleton {
};
}
function serializeScreenshot(): var {
return {
enable_pp: screenshot.enable_pp,
mode: screenshot.mode,
corner_radius: screenshot.corner_radius,
drop_shadow: screenshot.drop_shadow,
rounded_corners: screenshot.rounded_corners,
shadow_blur_radius: screenshot.shadow_blur_radius,
shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y
};
}
function serializeServices(): var {
return {
weatherLocation: services.weatherLocation,
@@ -429,6 +453,8 @@ Singleton {
}
property Overview overview: Overview {
}
property Screenshot screenshot: Screenshot {
}
property Services services: Services {
}
property SidebarConfig sidebar: SidebarConfig {
+35 -4
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import QtQuick
import ZShell
import qs.Helpers
@@ -79,10 +80,32 @@ Singleton {
}
function reloadHyprRules(): void {
const barStr = "keyword layerrule %1 %2, match:namespace ZShell-Bar";
const authStr = "keyword layerrule %1 %2, match:namespace ZShell-Auth";
Hypr.extras.batchMessage([barStr.arg("blur").arg(transparency.enabled ? 1 : 0), barStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
Hypr.extras.batchMessage([authStr.arg("blur").arg(transparency.enabled ? 1 : 0), authStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
const blur = transparency.enabled ? 1 : 0;
const alpha = transparency.base - 0.03;
const rules = `
hl.layer_rule({
match = { namespace = "ZShell-Bar" },
blur = ${blur}
})
hl.layer_rule({
match = { namespace = "ZShell-Bar" },
ignore_alpha = ${alpha}
})
hl.layer_rule({
match = { namespace = "ZShell-Auth" },
blur = ${blur}
})
hl.layer_rule({
match = { namespace = "ZShell-Auth" },
ignore_alpha = ${alpha}
})
`;
Hypr.extras.message(`eval ${rules}`);
}
function setMode(mode: string): void {
@@ -93,6 +116,14 @@ Singleton {
Component.onCompleted: debounceTimer.triggered()
Connections {
function onUsingLuaChanged(): void {
root.reloadHyprRules();
}
target: Hyprland
}
Connections {
function onConfigReloaded(): void {
root.reloadHyprRules();
+1
View File
@@ -6,6 +6,7 @@ JsonObject {
}
property Color color: Color {
}
property string dateFormat: "ddd d MMM - hh:mm:ss"
property bool desktopIcons: false
property Idle idle: Idle {
}
+2
View File
@@ -5,6 +5,8 @@ JsonObject {
property bool enableFprint: true
property int maxFprintTries: 3
property bool recolorLogo: false
property bool showNotifContent: false
property bool showNotifIcon: true
property Sizes sizes: Sizes {
}
+13
View File
@@ -0,0 +1,13 @@
import Quickshell.Io
JsonObject {
property real corner_radius: 12.0
property bool drop_shadow: true
property bool enable_pp: true
property string mode: "manual"
property bool rounded_corners: false
property real shadow_blur_radius: 22.0
property list<int> shadow_color: [0, 0, 0, 160]
property real shadow_offset_x: 5.0
property real shadow_offset_y: 5.0
}
+23 -174
View File
@@ -3,184 +3,33 @@ import QtQuick
Canvas {
id: root
property rect dirtyRect: Qt.rect(0, 0, 0, 0)
property bool frameQueued: false
property bool fullRepaintPending: true
property point lastPoint: Qt.point(0, 0)
property real minPointDistance: 2.0
property color penColor: "white"
property real penWidth: 4
property var pendingSegments: []
property bool strokeActive: false
property var strokes: []
property var points: []
function appendPoint(x, y) {
if (!strokeActive || strokes.length === 0)
function clear(): void {
var ctx = getContext('2d');
root.points = [];
ctx.reset();
root.requestPaint();
}
renderStrategy: Canvas.Cooperative
onPaint: {
if (points.length < 2)
return;
const dx = x - lastPoint.x;
const dy = y - lastPoint.y;
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance))
return;
const x1 = lastPoint.x;
const y1 = lastPoint.y;
const x2 = x;
const y2 = y;
strokes[strokes.length - 1].push(Qt.point(x2, y2));
pendingSegments.push({
dot: false,
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
lastPoint = Qt.point(x2, y2);
queueDirty(segmentDirtyRect(x1, y1, x2, y2));
}
function beginStroke(x, y) {
const p = Qt.point(x, y);
strokes.push([p]);
lastPoint = p;
strokeActive = true;
pendingSegments.push({
dot: true,
x: x,
y: y
});
queueDirty(pointDirtyRect(x, y));
}
function clear() {
strokes = [];
pendingSegments = [];
dirtyRect = Qt.rect(0, 0, 0, 0);
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function drawDot(ctx, x, y) {
ctx.beginPath();
ctx.arc(x, y, penWidth / 2, 0, Math.PI * 2);
ctx.fill();
}
function drawSegment(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function endStroke() {
strokeActive = false;
}
function pointDirtyRect(x, y) {
const pad = penWidth + 2;
return Qt.rect(x - pad, y - pad, pad * 2, pad * 2);
}
function queueDirty(r) {
dirtyRect = unionRects(dirtyRect, r);
if (frameQueued)
return;
frameQueued = true;
requestAnimationFrame(function () {
frameQueued = false;
if (dirtyRect.width > 0 && dirtyRect.height > 0) {
markDirty(dirtyRect);
dirtyRect = Qt.rect(0, 0, 0, 0);
}
});
}
function replayAll(ctx) {
ctx.clearRect(0, 0, width, height);
for (const stroke of strokes) {
if (!stroke || stroke.length === 0)
continue;
if (stroke.length === 1) {
const p = stroke[0];
drawDot(ctx, p.x, p.y);
continue;
}
ctx.beginPath();
ctx.moveTo(stroke[0].x, stroke[0].y);
for (let i = 1; i < stroke.length; ++i)
ctx.lineTo(stroke[i].x, stroke[i].y);
ctx.stroke();
}
}
function requestFullRepaint() {
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function segmentDirtyRect(x1, y1, x2, y2) {
const pad = penWidth + 2;
const left = Math.min(x1, x2) - pad;
const top = Math.min(y1, y2) - pad;
const right = Math.max(x1, x2) + pad;
const bottom = Math.max(y1, y2) + pad;
return Qt.rect(left, top, right - left, bottom - top);
}
function unionRects(a, b) {
if (a.width <= 0 || a.height <= 0)
return b;
if (b.width <= 0 || b.height <= 0)
return a;
const left = Math.min(a.x, b.x);
const top = Math.min(a.y, b.y);
const right = Math.max(a.x + a.width, b.x + b.width);
const bottom = Math.max(a.y + a.height, b.y + b.height);
return Qt.rect(left, top, right - left, bottom - top);
}
anchors.fill: parent
contextType: "2d"
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
onHeightChanged: requestFullRepaint()
onPaint: region => {
const ctx = getContext("2d");
var ctx = root.getContext('2d');
ctx.save();
ctx.lineWidth = root.penWidth;
ctx.strokeStyle = root.penColor;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = penWidth;
ctx.strokeStyle = penColor;
ctx.fillStyle = penColor;
if (fullRepaintPending) {
fullRepaintPending = false;
replayAll(ctx);
pendingSegments = [];
return;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 1; i < points.length; i++)
ctx.lineTo(points[i].x, points[i].y);
ctx.stroke();
points = points.slice(points.length - 2);
ctx.restore();
}
for (const seg of pendingSegments) {
if (seg.dot)
drawDot(ctx, seg.x, seg.y);
else
drawSegment(ctx, seg.x1, seg.y1, seg.x2, seg.y2);
}
pendingSegments = [];
}
onWidthChanged: requestFullRepaint()
}
+7 -5
View File
@@ -30,8 +30,10 @@ CustomMouseArea {
const x = event.x;
const y = event.y;
if (event.buttons & Qt.LeftButton)
root.drawing.appendPoint(x, y);
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
}
if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2;
@@ -44,7 +46,8 @@ CustomMouseArea {
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false;
root.drawing.beginStroke(x, y);
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return;
}
@@ -52,7 +55,6 @@ CustomMouseArea {
root.drawing.clear();
}
onReleased: {
if (root.visibilities.isDrawing)
root.drawing.endStroke();
root.drawing.points = [];
}
}
+15
View File
@@ -12,22 +12,37 @@ Scope {
required property ShellScreen screen
ExclusionZone {
id: top
anchors.top: true
exclusiveZone: root.bar.exclusiveZone
}
ExclusionZone {
id: left
anchors.left: true
}
ExclusionZone {
id: right
anchors.right: true
}
ExclusionZone {
id: bottom
anchors.bottom: true
}
Timer {
interval: 5000
running: true
onTriggered: console.log("top height:", top.exclusiveZone, "left width:", left.exclusiveZone, "right width:", right.exclusiveZone, "bottom height:", bottom.exclusiveZone)
}
component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border
implicitHeight: 1
+1 -1
View File
@@ -78,7 +78,7 @@ CustomMouseArea {
const dragY = y - dragStart.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
root.input.z = 2;
// root.input.z = 2;
root.panels.drawing.expanded = false;
}
+11
View File
@@ -34,6 +34,7 @@ Item {
readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen
readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar
readonly property alias toasts: toasts
readonly property alias utilities: utilities
@@ -176,6 +177,15 @@ Item {
visibilities: root.visibilities
}
Item {
id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
clip: true
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
implicitWidth: settings.implicitWidth
Settings.Wrapper {
id: settings
@@ -186,6 +196,7 @@ Item {
screen: root.screen
visibilities: root.visibilities
}
}
Dock.Wrapper {
id: dock
+40 -18
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import ZShell.Blobs
@@ -127,6 +128,14 @@ Variants {
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding {
property: "bar"
target: visibilities
@@ -149,6 +158,7 @@ Variants {
id: blobGroup
color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color {
CAnim {
@@ -170,28 +180,34 @@ Variants {
PanelBg {
id: dashBg
deformAmount: 0.08 * Config.appearance.deform.scale
implicitHeight: panels.dashboard.height
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width
panel: panels.dashboard
panel: panels.dashboardWrapper
radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
}
PanelBg {
id: launcherBg
deformAmount: 0.08 * Config.appearance.deform.scale
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher
radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
}
PanelBg {
id: sidebarBg
bottomLeftRadius: 0
deformAmount: 0.08 * Config.appearance.deform.scale
deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar
@@ -200,10 +216,10 @@ Variants {
PanelBg {
id: osdBg
deformAmount: 0.1 * Config.appearance.deform.scale
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osd
panel: panels.osdWrapper
radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
@@ -218,7 +234,7 @@ Variants {
PanelBg {
id: utilsBg
deformAmount: panels.sidebar.visible ? (0.1 * Config.appearance.deform.scale) : (0.1 * Config.appearance.deform.scale)
deformAmount: panels.sidebar.visible ? (0.1) : (0.1)
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities
topLeftRadius: 0
@@ -229,11 +245,11 @@ Variants {
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
deformAmount: panels.popouts.isDetached ? 0.05 * Config.appearance.deform.scale : panels.popouts.hasCurrent ? 0.15 * Config.appearance.deform.scale : 0.1 * Config.appearance.deform.scale
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width
panel: panels.popouts
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : Appearance.rounding.smallest
panel: panels.popoutsWrapper
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
@@ -246,10 +262,10 @@ Variants {
PanelBg {
id: resourcesBg
deformAmount: 0.08 * Config.appearance.deform.scale
deformAmount: 0.05
implicitHeight: panels.resources.height
implicitWidth: panels.resources.width
panel: panels.resources
panel: panels.resourcesWrapper
radius: Appearance.rounding.normal
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
@@ -258,17 +274,23 @@ Variants {
PanelBg {
id: settingsBg
deformAmount: 0.08 * Config.appearance.deform.scale
property real extraHeight: 0.2
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings
radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
id: dockBg
deformAmount: 0.08 * Config.appearance.deform.scale
deformAmount: 0.08
panel: panels.dock
radius: Appearance.rounding.normal
}
@@ -276,7 +298,7 @@ Variants {
PanelBg {
id: drawingBg
deformAmount: 0.08 * Config.appearance.deform.scale
deformAmount: 0.08
panel: panels.drawing
radius: Appearance.rounding.normal
}
@@ -371,7 +393,7 @@ Variants {
property real deformAmount: 0.15
required property Item panel
deformScale: deformAmount / 10000
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
group: blobGroup
implicitHeight: panel.height
implicitWidth: panel.width
+20 -20
View File
@@ -16,27 +16,14 @@ Scope {
property bool launching: false
property string promptMessage: ""
readonly property var selectedSession: sessionIndex >= 0 ? sessions[sessionIndex] : null
readonly property var selectedUser: Users.selectedUser
property int sessionIndex: sessions.length > 0 ? 0 : -1
property var sessions: []
readonly property string userFace: selectedUser ? selectedUser.face : ""
readonly property string username: Users.selectedUsername
// User handling - now uses the Users singleton
readonly property var users: Users.users
readonly property var selectedUser: Users.selectedUser
readonly property string username: Users.selectedUsername
readonly property string userFace: selectedUser ? selectedUser.face : ""
// User selection functions (delegate to Users singleton)
function selectUser(username: string): bool {
return Users.selectUser(username);
}
function selectNextUser(): void {
Users.selectNext();
}
function selectPreviousUser(): void {
Users.selectPrevious();
}
signal flashMsg
@@ -58,11 +45,11 @@ Scope {
event.accepted = true;
return;
}
if (event.text && !/[\r\n]/.test(event.text)) {
} else if (event.key === Qt.Key_Escape) {
buffer = "";
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
// No illegal characters (you are insane if you use unicode in your password)
buffer += event.text;
event.accepted = true;
}
}
@@ -81,6 +68,19 @@ Scope {
Greetd.launch(selectedSession.command, [], true);
}
function selectNextUser(): void {
Users.selectNext();
}
function selectPreviousUser(): void {
Users.selectPrevious();
}
// User selection functions (delegate to Users singleton)
function selectUser(username: string): bool {
return Users.selectUser(username);
}
function submit(): void {
errorMessage = "";
-1
View File
@@ -346,7 +346,6 @@ Singleton {
stdout: StdioCollector {
onStreamFinished: {
console.log("this is running");
if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
+20 -44
View File
@@ -7,23 +7,30 @@ import QtQuick
Singleton {
id: root
// The list of users that can log in graphically
// Each user object has: username, uid, home, shell, gecos (full name), face (avatar path)
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
property int selectedIndex: 0
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
property var users: []
// The currently selected user index
property int selectedIndex: 0
function saveDefaultUser(): void {
if (selectedUser) {
defaultUserStorage.setText(selectedUser.username);
}
}
// The currently selected user object (or null if none)
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
function selectNext(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
// Convenience property for the selected username
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
function selectPrevious(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex - 1 + users.length) % users.length;
}
// Path to store the default user preference
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
// Select a user by username
function selectUser(username: string): bool {
for (let i = 0; i < users.length; i++) {
if (users[i].username === username) {
@@ -34,28 +41,6 @@ Singleton {
return false;
}
// Select the next user in the list (wraps around)
function selectNext(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
// Select the previous user in the list (wraps around)
function selectPrevious(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex - 1 + users.length) % users.length;
}
// Save the current user as the default for next login
function saveDefaultUser(): void {
if (selectedUser) {
defaultUserStorage.setText(selectedUser.username);
}
}
// Process to fetch the list of graphical users
Process {
id: userLister
@@ -67,13 +52,10 @@ Singleton {
try {
root.users = JSON.parse(text);
// If we have users and no selection yet, try to select the default user
if (root.users.length > 0) {
// Try to load the default user
if (defaultUserStorage.loaded) {
const defaultUsername = defaultUserStorage.text().trim();
if (defaultUsername && !root.selectUser(defaultUsername)) {
// Default user not found, select first user
root.selectedIndex = 0;
}
} else {
@@ -87,15 +69,14 @@ Singleton {
}
}
// FileView for persisting the default user
FileView {
id: defaultUserStorage
path: root.defaultUserFile
preload: true
onLoadFailed: {}
onLoaded: {
// If users are already loaded, try to select the default user
if (root.users.length > 0) {
const defaultUsername = text().trim();
if (defaultUsername) {
@@ -103,10 +84,5 @@ Singleton {
}
}
}
onLoadFailed: {
// File doesn't exist yet, that's fine - we'll create it on first save
console.log("No default user file found, will use first user");
}
}
}
+72
View File
@@ -0,0 +1,72 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import ZShell
import qs.Config
Singleton {
id: root
property alias enabled: props.enabled
function setHyprConf(): void {
Hypr.extras.applyOptions({
"animations.enabled": 0,
"decoration.shadow.enabled": 0,
"decoration.blur.enabled": 0,
"general.border_size": 0,
"decoration.rounding": 0
});
}
onEnabledChanged: {
if (enabled) {
setHyprConf();
if (Config.utilities.toasts.gameModeChanged)
Toaster.toast(qsTr("Game mode enabled"), qsTr("Disabled Hyprland animations, blur, shadows and corner radius"), "gamepad");
} else {
Hypr.extras.message("reload");
if (Config.utilities.toasts.gameModeChanged)
Toaster.toast(qsTr("Game mode disabled"), qsTr("Hyprland settings restored"), "gamepad");
}
}
PersistentProperties {
id: props
property bool enabled: Hypr.options["animations:enabled"] === 0
reloadableId: "gamemode"
}
Connections {
function onConfigReloaded(): void {
if (props.enabled)
root.setHyprConf();
}
target: Hypr
}
IpcHandler {
function disable(): void {
props.enabled = false;
}
function enable(): void {
props.enabled = true;
}
function isEnabled(): bool {
return props.enabled;
}
function toggle(): void {
props.enabled = !props.enabled;
}
target: "gameMode"
}
}
-11
View File
@@ -1,11 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Networking
Singleton {
id: root
property NetworkDevice activeDevice: devices.find(d => d.connected)
property list<NetworkDevice> devices: Networking.devices.values
}
+2 -1
View File
@@ -66,7 +66,8 @@ MouseArea {
function save(): void {
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached(["swappy", "-f", path]));
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--image"] : ["swappy", "-f"];
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path]));
closeAnim.start();
}
+7 -2
View File
@@ -1,18 +1,23 @@
pragma Singleton
import Quickshell
import QtQuick
import qs.Config
Singleton {
readonly property string amPmStr: timeComponents[2] ?? ""
id: root
readonly property date date: clock.date
readonly property string dateStr: format(Config.general.dateFormat)
property alias enabled: clock.enabled
readonly property string hourStr: timeComponents[0] ?? ""
readonly property int hours: clock.hours
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes
readonly property string secondStr: timeComponents[2] ?? ""
readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm")
readonly property string timeStr: format("hh:mm:ss")
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
+14
View File
@@ -6,7 +6,21 @@ import Quickshell.Services.UPower
Singleton {
id: root
readonly property real batteryPercent: UPower.displayDevice.percentage
readonly property list<UPowerDevice> devices: UPower.devices.values
readonly property UPowerDevice displayDevice: UPower.displayDevice
readonly property bool onBattery: UPower.onBattery
// property bool toastShown
//
// Connections {
// target: UPower
//
// function onPercentageChanged(): {
// if (root.batteryPercent >= 0.2 && toastShown)
// return;
//
// root.toastShown = true;
// Toaster.toast(qsTr("Battery "))
// }
// }
}
+79
View File
@@ -11,10 +11,13 @@ Singleton {
id: root
property int availableUpdates: 0
property string cmd: ""
property bool commandReady
property bool loaded
property double now: Date.now()
property var updates: ({})
property bool updating
property string updatingPackage: ""
function formatUpdateTime(timestamp) {
const diffMs = root.now - timestamp;
@@ -34,6 +37,22 @@ Singleton {
return Qt.formatDateTime(new Date(timestamp), "dd hh:mm");
}
function performPackageUpdate(pkg: string): void {
if (root.cmd === "pacman")
pkgUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Sy", pkg];
else
pkgUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Sy", pkg];
pkgUpdateProc.running = true;
}
function performSystemUpdate(): void {
if (root.cmd === "pacman")
sysUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Syu"];
else
sysUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Syu"];
sysUpdateProc.running = true;
}
onUpdatesChanged: {
if (!root.loaded)
return;
@@ -92,6 +111,28 @@ Singleton {
}
}
Process {
id: updateCmdDetect
command: ["sh", "-c", "command -v yay || command -v paru"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const cmd = this.text.trim();
let helper;
if (cmd.length > 0) {
helper = cmd.split("/").pop();
} else {
helper = "pacman";
}
root.cmd = helper;
}
}
}
Process {
id: updatesProc
@@ -115,6 +156,44 @@ Singleton {
}
}
Process {
id: sysUpdateProc
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
root.updating = false;
}
}
onRunningChanged: {
if (running)
root.updating = true;
}
}
Process {
id: pkgUpdateProc
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
root.updating = false;
}
}
onRunningChanged: {
if (running) {
root.updatingPackage = command[command.length - 1];
root.updating = true;
}
}
}
Timer {
id: saveTimer
+51
View File
@@ -1,7 +1,9 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import QtQuick
import ZShell.Models
import qs.Config
import qs.Modules
@@ -12,10 +14,17 @@ Searcher {
id: root
property string actualCurrent: WallpaperPath.currentWallpaperPath
property alias crops: adapter.monitorCrops
readonly property string current: showPreview ? previewPath : actualCurrent
property alias monitorCrops: monitorCrops
property string previewPath
property bool recentlyChanged
property bool showPreview: false
function getCrop(screen: string): var {
return root.crops[screen];
}
function preview(path: string): void {
previewPath = path;
if (Config.general.color.schemeGeneration)
@@ -23,9 +32,35 @@ Searcher {
showPreview = true;
}
function setCrop(screen: string, rect: rect, scaledRect: rect, zoom: real): void {
let updated = Object.assign({}, root.crops);
if (zoom <= 0)
zoom = 1.0;
else if (zoom > 5.0)
zoom = 5.0;
updated[screen] = {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
scaledX: scaledRect.x,
scaledY: scaledRect.y,
scaledWidth: scaledRect.width,
scaledHeight: scaledRect.height,
zoom: zoom
};
root.crops = updated;
monitorCrops.writeAdapter();
monitorCrops.reload();
}
function setWallpaper(path: string): void {
actualCurrent = path;
WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 0, 0), Qt.rect(0, 0, 0, 0), 1.0));
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
@@ -52,6 +87,22 @@ Searcher {
target: "wallpaper"
}
FileView {
id: monitorCrops
path: `${Paths.state}/wallpaper-crops.json`
watchChanges: true
onAdapterUpdated: writeAdapter()
onFileChanged: reload()
JsonAdapter {
id: adapter
property var monitorCrops: ({})
}
}
FileSystemModel {
id: wallpapers
+1 -1
View File
@@ -3,7 +3,7 @@ import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Modules
import qs.Helpers as Helpers
import qs.Helpers
import qs.Components
CustomRect {
-31
View File
@@ -1,31 +0,0 @@
import Quickshell
import QtQuick
import qs.Config
import qs.Helpers
import qs.Components
CustomRect {
id: root
required property PersistentProperties visibilities
anchors.bottom: parent.bottom
anchors.bottomMargin: 6
anchors.top: parent.top
anchors.topMargin: 6
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: 40
radius: Appearance.rounding.full
StateLayer {
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurface
text: "widgets"
}
}
+22 -74
View File
@@ -12,90 +12,29 @@ Item {
required property Canvas drawing
property bool expanded: true
property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities
anchors.leftMargin: (-implicitWidth - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: 0
visible: width > 0
implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth
opacity: 1 - offsetScale
visible: offsetScale < 1
states: [
State {
name: "hidden"
when: !root.shouldBeActive
PropertyChanges {
root.implicitWidth: 0
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "collapsed"
when: root.shouldBeActive && !root.expanded
PropertyChanges {
root.implicitWidth: icon.implicitWidth
}
PropertyChanges {
icon.opacity: 1
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "visible"
when: root.shouldBeActive && root.expanded
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 1
}
}
]
transitions: [
Transition {
from: "*"
to: "*"
ParallelAnimation {
Behavior on implicitWidth {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
]
onVisibleChanged: {
if (!visible)
@@ -109,8 +48,12 @@ Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height
opacity: 1
opacity: root.expanded ? 0 : 1
Behavior on opacity {
Anim {
}
}
sourceComponent: MaterialIcon {
font.pointSize: Appearance.font.size.larger
text: "arrow_forward_ios"
@@ -122,7 +65,12 @@ Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
opacity: root.expanded ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: Content {
drawing: root.drawing
visibilities: root.visibilities
-1
View File
@@ -13,7 +13,6 @@ Searcher {
function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id);
console.log(root.command);
if (entry.runInTerminal)
Quickshell.execDetached({
+14 -9
View File
@@ -8,21 +8,25 @@ import qs.Helpers
Scope {
id: root
readonly property bool enabled: !Players.list.some(p => p.isPlaying)
required property Lock lock
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
function handleIdleAction( action: var ): void {
if ( !action )
function handleIdleAction(action: var): void {
if (!action)
return;
if ( action === "lock" )
if (action === "lock")
lock.lock.locked = true;
else if ( action === "unlock" )
else if (action === "unlock")
lock.lock.locked = false;
else if ( typeof action === "string" )
Hypr.dispatch( action );
else if (action === "dpms on")
Hypr.dispatch('hl.dsp.dpms({ action = "enable" })');
else if (action === "dpms off")
Hypr.dispatch('hl.dsp.dpms({ action = "disable" })');
else if (typeof action === "string")
Hypr.dispatch(action);
else
Quickshell.execDetached( action );
Quickshell.execDetached(action);
}
Variants {
@@ -33,7 +37,8 @@ Scope {
enabled: root.enabled && modelData.timeout > 0 ? true : false
timeout: modelData.timeout
onIsIdleChanged: root.handleIdleAction( isIdle ? modelData.idleAction : modelData.activeAction )
onIsIdleChanged: root.handleIdleAction(isIdle ? modelData.idleAction : modelData.activeAction)
}
}
}
+3
View File
@@ -58,6 +58,7 @@ CustomRect {
fillMode: Image.PreserveAspectCrop
height: Config.notifs.sizes.image
source: Qt.resolvedUrl(root.image)
visible: Config.lock.showNotifIcon
width: Config.notifs.sizes.image
}
}
@@ -284,6 +285,8 @@ CustomRect {
Layout.fillWidth: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: {
if (!Config.lock.showNotifContent)
return "Unlock to view";
const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " ");
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
+2
View File
@@ -30,6 +30,8 @@ Scope {
} else {
buffer = buffer.slice(0, -1);
}
} else if (event.key === Qt.Key_Escape) {
buffer = "";
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
// No illegal characters (you are insane if you use unicode in your password)
buffer += event.text;
+3 -9
View File
@@ -10,7 +10,6 @@ Item {
readonly property int padding: 6
required property Item panels
required property Item sidebarPanel
required property PersistentProperties visibilities
anchors.bottom: parent.bottom
@@ -44,7 +43,7 @@ Item {
return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
}
implicitWidth: Math.max(sidebarPanel.width * (1 - sidebarPanel.offsetScale), Config.notifs.sizes.width + padding * 2)
implicitWidth: Config.notifs.sizes.width + padding * 2
Behavior on implicitHeight {
Anim {
@@ -73,9 +72,8 @@ Item {
required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8)
implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start()
onIndexChanged: {
@@ -126,20 +124,16 @@ Item {
}
ClippingRectangle {
// implicitWidth: notif.implicitWidth
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: wrapper.idx === 0 ? 0 : 8
color: "transparent"
implicitHeight: notif.implicitHeight
implicitWidth: notif.implicitWidth
radius: Appearance.rounding.smallest / 2
Notification {
id: notif
anchors.left: parent.left
anchors.right: parent.right
modelData: wrapper.modelData
}
}
+1 -1
View File
@@ -20,9 +20,9 @@ CustomRect {
required property NotifServer.Notif modelData
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
// implicitWidth: Config.notifs.sizes.width
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: inner.implicitHeight
implicitWidth: Config.notifs.sizes.width
radius: 6
x: Config.notifs.sizes.width
@@ -78,6 +78,10 @@ LazyListView {
}
}
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
notifInner.toggleExpand(!notifInner.expanded);
}
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
@@ -79,6 +79,10 @@ LazyListView {
Component.onCompleted: modelData?.lock(this)
Component.onDestruction: modelData?.unlock(this)
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
root.requestToggleExpand(!root.expanded);
}
onPositionChanged: event => {
if (pressed && !root.expanded) {
const diffY = event.y - startY;
@@ -5,6 +5,7 @@ import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules
import qs.Helpers
import qs.Daemons
CustomRect {
@@ -32,7 +33,7 @@ CustomRect {
Toggle {
checked: Network.wifiEnabled
icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.length > 0
visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi)
onClicked: Network.toggleWifi()
}
@@ -79,6 +80,13 @@ CustomRect {
adapter.enabled = !adapter.enabled;
}
}
Toggle {
checked: GameMode.enabled
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: GameMode.enabled = !GameMode.enabled
}
}
}
-1
View File
@@ -17,7 +17,6 @@ Item {
id: content
panels: root.panels
sidebarPanel: root.sidebarPanel
visibilities: root.visibilities
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ CustomRect {
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
radius: height / 2
radius: Appearance.rounding.full
StateLayer {
onClicked: root.visibilities.resources = !root.visibilities.resources
+18
View File
@@ -104,6 +104,18 @@ Item {
key: "launcher"
name: "Launcher"
}
ListElement {
icon: "screenshot_region"
key: "screenshot"
name: "Screenshot"
}
ListElement {
icon: "cached"
key: "updates"
name: "Updates"
}
}
CustomClippingRect {
@@ -171,9 +183,15 @@ Item {
Layout.fillHeight: true
Layout.preferredWidth: icon.contentWidth
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: categoryItem.index === clayout.currentIndex ? 1 : 0
font.pointSize: Appearance.font.size.small * 2
text: categoryItem.icon
verticalAlignment: Text.AlignVCenter
Behavior on fill {
Anim {
}
}
}
CustomText {
+8 -7
View File
@@ -1,3 +1,4 @@
import Quickshell
import QtQuick.Layouts
import qs.Modules.Settings.Controls
import qs.Config
@@ -5,6 +6,8 @@ import qs.Config
SettingsPage {
id: root
required property ShellScreen screen
SettingsSection {
sectionId: "Wallpaper"
@@ -29,13 +32,11 @@ SettingsPage {
step: 50
}
// Separator {
// }
//
// WallpaperCropper {
// Layout.fillWidth: true
// Layout.preferredHeight: 300
// }
Separator {
}
WallpaperCropper {
}
}
SettingsSection {
+16 -6
View File
@@ -19,8 +19,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Height"
min: 1
name: "Height"
object: Config.barConfig
setting: "height"
}
@@ -29,8 +29,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Rounding"
min: 0
name: "Rounding"
object: Config.barConfig
setting: "rounding"
}
@@ -39,11 +39,21 @@ SettingsPage {
}
SettingSpinBox {
name: "Border"
min: 0
name: "Border"
object: Config.barConfig
setting: "border"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.barConfig
setting: "smoothing"
}
}
SettingsSection {
@@ -145,8 +155,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Dock height"
min: 1
name: "Dock height"
object: Config.dock
setting: "height"
}
@@ -173,8 +183,8 @@ SettingsPage {
}
SettingStringList {
name: "Pinned apps"
addLabel: qsTr("Add pinned app")
name: "Pinned apps"
object: Config.dock
setting: "pinnedApps"
}
@@ -183,8 +193,8 @@ SettingsPage {
}
SettingStringList {
name: "Ignored app regexes"
addLabel: qsTr("Add ignored regex")
name: "Ignored app regexes"
object: Config.dock
setting: "ignoredAppRegexes"
}
+9
View File
@@ -47,6 +47,15 @@ SettingsPage {
object: Config.general
setting: "desktopIcons"
}
Separator {
}
SettingInput {
name: "Date format"
object: Config.general
setting: "dateFormat"
}
}
SettingsSection {
+23 -5
View File
@@ -31,8 +31,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Max fingerprint tries"
min: 1
name: "Max fingerprint tries"
object: Config.lock
setting: "maxFprintTries"
step: 1
@@ -41,9 +41,27 @@ SettingsPage {
Separator {
}
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSwitch {
name: "Show notification icon"
object: Config.lock
setting: "showNotifIcon"
}
Separator {
}
SettingSpinBox {
name: "Blur amount"
min: 0
name: "Blur amount"
object: Config.lock
setting: "blurAmount"
step: 1
@@ -53,9 +71,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Height multiplier"
max: 2
min: 0.1
name: "Height multiplier"
object: Config.lock.sizes
setting: "heightMult"
step: 0.05
@@ -65,9 +83,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Aspect ratio"
max: 4
min: 0.5
name: "Aspect ratio"
object: Config.lock.sizes
setting: "ratio"
step: 0.05
@@ -77,8 +95,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Center width"
min: 100
name: "Center width"
object: Config.lock.sizes
setting: "centerWidth"
step: 10
@@ -22,6 +22,13 @@ ColumnLayout {
Config.save();
}
function deleteTimeoutEntry(index) {
let list = [...Config.general.idle.timeouts];
list.splice(index, 1);
Config.general.idle.timeouts = list;
Config.save();
}
function updateTimeoutEntry(i, key, value) {
const list = [...Config.general.idle.timeouts];
let entry = list[i];
@@ -49,6 +56,9 @@ ColumnLayout {
onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", "");
}
onDeleteRequested: function (index) {
root.deleteTimeoutEntry(index);
}
onFieldEdited: function (key, value) {
root.updateTimeoutEntry(index, key, value);
}
+130
View File
@@ -0,0 +1,130 @@
import qs.Modules.Settings.Controls
import qs.Config
import qs.Components
SettingsPage {
SettingsSection {
sectionId: "Screenshot"
SettingsHeader {
name: "Screenshot"
}
SettingSwitch {
name: "Enable effects"
object: Config.screenshot
setting: "enable_pp"
}
Separator {
}
CustomSplitButtonRow {
// active: true
label: qsTr("Effects mode")
menuItems: [
MenuItem {
icon: "build"
text: qsTr("Manual")
value: "manual"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Auto")
value: "auto"
}
]
onSelected: item => {
Config.screenshot.mode = item.value;
Config.save();
}
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Corner radius"
object: Config.screenshot
setting: "corner_radius"
step: 1
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Enable drop shadow"
object: Config.screenshot
setting: "drop_shadow"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounded_corners"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Shadow blur radius"
object: Config.screenshot
setting: "shadow_blur_radius"
step: 1
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Shadow color broken atm"
object: Config.Screenshot
setting: "shadow_color"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Shadow offset X"
object: Config.screenshot
setting: "shadow_offset_x"
step: 1
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Shadow offset Y"
object: Config.screenshot
setting: "shadow_offset_y"
step: 1
visible: Config.screenshot.mode === "manual"
}
}
}
@@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick.Layouts
import QtQuick
import qs.Config
import qs.Helpers
import qs.Components
import qs.Modules.Settings.Controls
CustomClippingRect {
id: root
radius: Appearance.rounding.normal - Appearance.padding.smaller
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Layout.margins: Appearance.padding.large
spacing: Appearance.spacing.large
MaterialIcon {
font.pointSize: Appearance.font.size.larger * 4
text: "update"
}
ColumnLayout {
CustomText {
font.pointSize: Appearance.font.size.large * 2
text: "System updates"
}
RowLayout {
id: row
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: `${Updates.availableUpdates} available updates`
}
CustomRect {
Layout.preferredHeight: 40
Layout.preferredWidth: 150
color: Updates.updating ? DynamicColors.layer(DynamicColors.palette.m3outline, 2) : DynamicColors.palette.m3primary
radius: Appearance.rounding.full
RowLayout {
anchors.centerIn: parent
MaterialIcon {
animate: true
color: DynamicColors.palette.m3onPrimary
font.pointSize: Appearance.font.size.large
text: Updates.updating ? "update" : "download"
}
CustomText {
color: Updates.updating ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onPrimary
text: "Update all"
}
}
StateLayer {
color: DynamicColors.palette.m3onPrimary
disabled: Updates.updating
onClicked: Updates.performSystemUpdate()
}
}
}
}
}
CustomListView {
id: view
readonly property int itemHeight: 50 + Appearance.padding.smaller * 2
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
contentHeight: height
spacing: Appearance.spacing.normal
delegate: CustomRect {
id: update
required property var modelData
readonly property list<string> sections: modelData.update.split(" ")
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: view.itemHeight
implicitWidth: parent.width
radius: Appearance.rounding.small - Appearance.padding.small
RowLayout {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.smaller
MaterialIcon {
font.pointSize: Appearance.font.size.large * 2
text: "package_2"
}
ColumnLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.preferredHeight: 25
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: update.sections[0]
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
text: Updates.formatUpdateTime(update.modelData.timestamp)
}
}
RowLayout {
Layout.fillHeight: true
Layout.preferredWidth: 500
MarqueeText {
id: versionFrom
Layout.fillHeight: true
Layout.preferredWidth: 225
animate: true
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[1]
width: 225
}
MaterialIcon {
Layout.fillHeight: true
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter
}
MarqueeText {
id: versionTo
Layout.fillHeight: true
Layout.preferredWidth: 225
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[3]
width: 225
}
}
IconButton {
Layout.preferredHeight: width
icon: "download"
onClicked: {
Updates.performPackageUpdate(update.sections[0]);
}
}
}
}
model: ScriptModel {
id: script
objectProp: "update"
values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({
update,
timestamp
}))
}
}
}
}
+21 -1
View File
@@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
@@ -20,7 +22,6 @@ Item {
required property PersistentProperties visibilities
function scrollToSetting(section: string, settingName: string) {
// Wait for the StackView transition to complete, then scroll
root.pendingSection = section;
root.pendingSetting = settingName;
scrollTimer.restart();
@@ -74,6 +75,10 @@ Item {
stack.push(osd);
else if (currentCategory === "launcher")
stack.push(launcher);
else if (currentCategory === "screenshot")
stack.push(screenshot);
else if (currentCategory === "updates")
stack.push(updates);
}
target: root
@@ -153,6 +158,7 @@ Item {
id: background
Cat.Background {
screen: root.screen
}
}
@@ -225,4 +231,18 @@ Item {
Cat.Launcher {
}
}
Component {
id: screenshot
Cat.Screenshot {
}
}
Component {
id: updates
Cat.SystemUpdates {
}
}
}
+29 -346
View File
@@ -28,72 +28,11 @@ Item {
property int uidCounter: 0
property var visualEntries: []
function beginVisualDrag(uid, modelData, item) {
const pos = item.mapToItem(root, 0, 0);
root.draggedUid = uid;
root.draggedModelData = modelData;
root.dragHeight = item.height;
root.dragStartX = pos.x;
root.dragStartY = pos.y;
root.dragX = pos.x;
root.dragY = pos.y;
root.dragActive = true;
root.dropAnimating = false;
root.pendingCommitEntries = [];
}
function commitVisualOrder(entries) {
const list = [];
for (let i = 0; i < entries.length; i++)
list.push(entries[i].entry);
root.object[root.setting] = list;
Config.save();
root.rebuildVisualEntries();
}
function endVisualDrag() {
const entries = root.visualEntries.slice();
const finalIndex = root.indexForUid(root.draggedUid);
const finalItem = listView.itemAtIndex(finalIndex);
root.dragActive = false;
if (!finalItem) {
root.pendingCommitEntries = entries;
root.finishVisualDrag();
return;
}
const pos = finalItem.mapToItem(root, 0, 0);
root.pendingCommitEntries = entries;
root.dropAnimating = true;
settleX.to = pos.x;
settleY.to = pos.y;
settleAnim.start();
}
function ensureVisualEntries() {
if (!root.dragActive && !root.dropAnimating)
root.rebuildVisualEntries();
}
function finishVisualDrag() {
const entries = root.pendingCommitEntries.slice();
root.dragActive = false;
root.dropAnimating = false;
root.draggedUid = "";
root.draggedModelData = null;
root.pendingCommitEntries = [];
root.dragHeight = 0;
root.commitVisualOrder(entries);
}
function iconForId(id) {
switch (id) {
case "workspaces":
@@ -153,7 +92,7 @@ Item {
case "spacer":
return qsTr("Spacer");
case "activeWindow":
return qsTr("Active window");
return qsTr("Title");
case "tray":
return qsTr("Tray");
case "upower":
@@ -163,34 +102,14 @@ Item {
case "clock":
return qsTr("Clock");
case "notifBell":
return qsTr("Notification bell");
return qsTr("Notifs");
case "hyprsunset":
return qsTr("Night light");
default:
return id;
}
}
function moveArrayItem(list, from, to) {
const next = list.slice();
const [item] = next.splice(from, 1);
next.splice(to, 0, item);
return next;
}
function previewVisualMove(from, hovered, before) {
let to = hovered + (before ? 0 : 1);
if (to > from)
to -= 1;
to = Math.max(0, Math.min(visualModel.items.count - 1, to));
if (from === to)
return;
visualModel.items.move(from, to);
root.visualEntries = root.moveArrayItem(root.visualEntries, from, to);
}
function rebuildVisualEntries() {
const entries = root.object[root.setting] ?? [];
const next = [];
@@ -225,7 +144,6 @@ Item {
list[index] = entry;
root.object[root.setting] = list;
Config.save();
root.ensureVisualEntries();
}
Layout.fillWidth: true
@@ -233,7 +151,7 @@ Item {
Component.onCompleted: root.rebuildVisualEntries()
Rectangle {
CustomRect {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
@@ -248,39 +166,12 @@ Item {
}
}
ParallelAnimation {
id: settleAnim
onFinished: root.finishVisualDrag()
Anim {
id: settleX
duration: Appearance.anim.durations.normal
property: "dragX"
target: root
}
Anim {
id: settleY
duration: Appearance.anim.durations.normal
property: "dragY"
target: root
}
}
ColumnLayout {
RowLayout {
id: layout
anchors.fill: parent
spacing: Appearance.spacing.smaller
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
// spacing: Appearance.spacing.smaller
DelegateModel {
id: visualModel
@@ -293,248 +184,40 @@ Item {
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
boundsBehavior: Flickable.StopAtBounds
clip: false
implicitHeight: contentHeight
implicitWidth: width
interactive: !(root.dragActive || root.dropAnimating)
Repeater {
delegate: entryDelegate
model: visualModel
spacing: Appearance.spacing.small
add: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
displaced: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
move: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
removeDisplaced: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
}
}
Loader {
active: root.dragActive || root.dropAnimating
asynchronous: false
sourceComponent: Item {
Drag.active: root.dragActive
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
height: proxyRect.implicitHeight
implicitHeight: proxyRect.implicitHeight
implicitWidth: listView.width
visible: root.draggedModelData !== null
width: listView.width
x: root.dragX
y: root.dragY
z: 100
Drag.source: QtObject {
property string uid: root.draggedUid
property int visualIndex: root.indexForUid(root.draggedUid)
}
CustomRect {
id: proxyRect
color: DynamicColors.tPalette.m3surface
implicitHeight: proxyRow.implicitHeight + Appearance.padding.small * 2
implicitWidth: parent.width
opacity: 0.95
radius: Appearance.rounding.normal
width: parent.width
RowLayout {
id: proxyRow
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
CustomRect {
color: Qt.alpha(DynamicColors.palette.m3onSurface, 0.12)
implicitHeight: 32
implicitWidth: implicitHeight
radius: Appearance.rounding.small
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
text: "drag_indicator"
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
text: root.iconForId(root.draggedModelData?.entry?.id ?? "")
}
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.labelForId(root.draggedModelData?.entry?.id ?? "")
}
CustomSwitch {
checked: root.draggedModelData?.entry?.enabled ?? true
enabled: false
}
}
}
}
}
Component {
id: entryDelegate
DropArea {
id: slot
readonly property var entryData: modelData.entry
IconButton {
required property int index
required property var modelData
readonly property string uid: modelData.uid
function previewReorder(drag) {
const source = drag.source;
if (!source || !source.uid || source.uid === slot.uid)
return;
const from = source.visualIndex;
const hovered = slot.DelegateModel.itemsIndex;
if (from < 0 || hovered < 0)
return;
root.previewVisualMove(from, hovered, drag.y < height / 2);
}
height: entryRow.implicitHeight
implicitHeight: entryRow.implicitHeight
implicitWidth: listView.width
width: ListView.view ? ListView.view.width : listView.width
onEntered: drag => previewReorder(drag)
onPositionChanged: drag => previewReorder(drag)
CustomRect {
id: entryRow
anchors.fill: parent
color: DynamicColors.tPalette.m3surface
implicitHeight: entryLayout.implicitHeight + Appearance.padding.small * 2
implicitWidth: parent.width
opacity: root.draggedUid === slot.uid ? 0 : 1
radius: Appearance.rounding.full
Behavior on opacity {
Anim {
}
}
RowLayout {
id: entryLayout
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
CustomRect {
id: handle
color: Qt.alpha(DynamicColors.palette.m3onSurface, handleDrag.active ? 0.12 : handleHover.hovered ? 0.09 : 0.06)
implicitHeight: 32
implicitWidth: implicitHeight
radius: Appearance.rounding.full
Behavior on color {
CAnim {
}
}
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
text: "drag_indicator"
}
HoverHandler {
id: handleHover
cursorShape: handleDrag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor
}
DragHandler {
id: handleDrag
enabled: true
grabPermissions: PointerHandler.CanTakeOverFromAnything
target: null
xAxis.enabled: false
yAxis.enabled: true
onActiveChanged: {
if (active) {
root.beginVisualDrag(slot.uid, slot.modelData, entryRow);
} else if (root.draggedUid === slot.uid) {
root.endVisualDrag();
}
}
onActiveTranslationChanged: {
if (!active || root.draggedUid !== slot.uid)
return;
root.dragX = root.dragStartX;
root.dragY = root.dragStartY + activeTranslation.y;
}
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
text: root.iconForId(slot.entryData.id)
}
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.labelForId(slot.entryData.id)
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
checked: modelData.entry.enabled ?? true
font: Appearance.font.family.sans
// icon: root.iconForId(modelData.entry.id)
icon: root.labelForId(modelData.entry.id)
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
toggle: true
visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix))
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
CustomSwitch {
Layout.rightMargin: Appearance.padding.small
checked: slot.entryData.enabled ?? true
onToggled: root.updateEntry(slot.DelegateModel.itemsIndex, checked)
}
}
}
onClicked: root.updateEntry(index, internalChecked)
}
}
}
+31 -8
View File
@@ -10,6 +10,7 @@ Item {
required property var modelData
signal addActiveActionRequested
signal deleteRequested(int index)
signal fieldEdited(string key, var value)
Layout.fillWidth: true
@@ -65,42 +66,64 @@ Item {
HoverHandler {
id: nameHover
}
HoverIconButton {
id: editButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
icon: "edit"
shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit()
}
CustomText {
anchors.left: parent.left
anchors.right: editButton.left
anchors.leftMargin: nameHover.hovered ? editButton.width + Appearance.spacing.smaller * 2 : 0
anchors.right: deleteButton.left
anchors.rightMargin: Appearance.spacing.small
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight // enable if CustomText supports it
font.pointSize: Appearance.font.size.larger
text: root.modelData.name
visible: !nameCell.editing
Behavior on anchors.leftMargin {
Anim {
}
}
}
IconButton {
id: editButton
HoverIconButton {
id: deleteButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
icon: "edit"
visible: nameHover.hovered && !nameCell.editing
icon: "delete"
shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit()
onClicked: root.deleteRequested(root.index)
}
CustomRect {
anchors.fill: parent
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surface
implicitHeight: nameEditor.implicitHeight + (Appearance.padding.normal * 2)
implicitWidth: Math.min(nameEditor.contentWidth + (Appearance.padding.normal * 2), parent.width - Appearance.padding.normal)
radius: Appearance.rounding.small
visible: nameCell.editing
CustomTextField {
id: nameEditor
anchors.fill: parent
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, nameCell.width - Appearance.padding.normal)
text: nameCell.draftName
Keys.onEscapePressed: {
@@ -38,7 +38,7 @@ Item {
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer
radius: 21 + Appearance.padding.normal
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
CustomRect {
id: searchBox
+230 -71
View File
@@ -1,102 +1,261 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Hyprland
import ZShell.Internal
import qs.Config
import qs.Components
import qs.Helpers
ColumnLayout {
Item {
id: wrapper
property bool changesMade: false
signal requestCrop
Layout.fillWidth: true
Layout.preferredHeight: 400
IconButton {
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
icon: "check"
opacity: wrapper.changesMade ? 1 : 0
scale: wrapper.changesMade ? 1 : 0
z: 2
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onClicked: {
wrapper.requestCrop();
wrapper.changesMade = false;
}
}
RowLayout {
id: root
spacing: 15
width: Math.min(parent ? parent.width : 600, 600)
anchors.fill: parent
spacing: Appearance.spacing.normal
Rectangle {
id: previewContainer
Repeater {
model: ScriptModel {
values: [...Quickshell.screens].sort((a, b) => {
return a.x - b.x;
})
}
Item {
id: delegate
required property ShellScreen modelData
function applyCrop(): void {
const croprect = cropRect.mapToItem(scaledImg, 0, 0, cropRect.width, cropRect.height);
const upscaledRect = Qt.rect((croprect.x - cropRect.imageX) / scaledImg.paintedWidth, (croprect.y - cropRect.imageY) / scaledImg.paintedHeight, croprect.width / scaledImg.paintedWidth, croprect.height / scaledImg.paintedHeight);
Wallpapers.setCrop(delegate.modelData.name, upscaledRect, croprect, cropRect.zoom);
}
function zoomClipRect(zoom: real): void {
let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5;
cropRect.zoom = zoom;
cropRect.x = oldCenterX - cropRect.width * 0.5;
cropRect.y = oldCenterY - cropRect.height * 0.5;
cropRect.clampToBounds();
}
Layout.fillHeight: true
Layout.preferredWidth: height * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 16 / 9)
clip: true
color: DynamicColors.palette.m3surfaceContainer
radius: Config.appearance.rounding.scale * 10
Layout.fillWidth: true
Image {
id: img
Connections {
function onRequestCrop(): void {
delegate.applyCrop();
}
anchors.fill: parent
target: wrapper
}
RowLayout {
id: sliderLayout
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 30
spacing: Appearance.spacing.large
CustomText {
text: qsTr("Crop scale")
}
CustomSlider {
id: zoomSlider
Layout.fillWidth: true
Layout.preferredHeight: 10
from: 1.0
to: 5.0
value: cropRect.zoom
onMoved: {
delegate.zoomClipRect(value);
wrapper.changesMade = true;
}
}
}
CachingImage {
id: scaledImg
property var displayData
property real monitorScale: 1.0
anchors.bottom: sliderLayout.top
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
asynchronous: true
fillMode: Image.PreserveAspectFit
// retainWhileLoading: true
source: Wallpapers.current
sourceSize.height: parent.height
sourceSize.width: parent.width
Rectangle {
onPaintedWidthChanged: {
if (paintedWidth > 0) {
scaledImg.displayData = Wallpapers.getCrop(delegate.modelData.name);
cropRect.zoom = Wallpapers.getCrop(delegate.modelData.name).zoom;
cropRect.restoreFromData();
}
}
onSourceChanged: cropRect.clampToBounds()
onStatusChanged: if (scaledImg.status == Image.Ready)
cropRect.clampToBounds()
CustomText {
id: monitorId
anchors.centerIn: parent
color: Qt.alpha(DynamicColors.palette.m3surface, 0.85)
font.pointSize: Appearance.font.size.large * 4
style: Text.Outline
styleColor: DynamicColors.palette.m3onSurface
text: delegate.modelData.name
}
CustomRect {
id: cropRect
property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom
property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom
property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight)
property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height
property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width
property real paintedX: (img.width - paintedWidth) / 2
property real paintedY: (img.height - paintedHeight) / 2
property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16 / 9
property real aspectRatio: delegate.modelData.width / delegate.modelData.height
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) {
fittedWidth = scaledImg.paintedWidth;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: scaledImg.displayData.zoom
function centerInImage() {
x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
}
function restoreFromData() {
let data = scaledImg.displayData;
if (data && data.scaledX !== 0 || data.scaledY !== 0 || data.scaledWidth !== 0 || data.scaledHeight !== 0) {
x = data.scaledX;
y = data.scaledY;
clampToBounds();
} else {
zoom = 1.0;
centerInImage();
}
}
border.color: DynamicColors.palette.m3primary
border.width: 2
color: Qt.alpha(DynamicColors.palette.m3primaryContainer, 0.3)
height: cropHeight
width: cropWidth
x: paintedX + (paintedWidth - width) * Config.background.alignX
y: paintedY + (paintedHeight - height) * Config.background.alignY
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
DragHandler {
target: null
onActiveTranslationChanged: {
if (active) {
let newX = cropRect.x - cropRect.paintedX + translation.x;
let newY = cropRect.y - cropRect.paintedY + translation.y;
let rangeX = cropRect.paintedWidth - cropRect.width;
let rangeY = cropRect.paintedHeight - cropRect.height;
if (rangeX > 0) {
let valX = newX / rangeX;
Config.background.alignX = Math.max(0.0, Math.min(1.0, valX));
Config.save();
}
if (rangeY > 0) {
let valY = newY / rangeY;
Config.background.alignY = Math.max(0.0, Math.min(1.0, valY));
Config.save();
}
}
Behavior on opacity {
Anim {
}
}
PinchHandler {
maximumScale: 5.0
minimumScale: 1.0
target: null
onActiveScaleChanged: {
if (active) {
let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1));
Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0));
}
}
}
}
}
Component.onCompleted: clampToBounds()
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
SettingSpinBox {
max: 5.0
min: 1.0
name: "Zoom"
object: Config.background
setting: "zoom"
step: 0.1
MouseArea {
id: mouse
function updateCrop(mouseX, mouseY) {
let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
}
}
}
}
}
}
}
+28 -34
View File
@@ -23,26 +23,13 @@ GridView {
delegate: Item {
required property int index
readonly property bool isCurrent: modelData && modelData.path === Wallpapers.actualCurrent
readonly property real itemMargin: Appearance.spacing.normal / 2
readonly property real itemRadius: Appearance.rounding.normal
readonly property real itemMargin: Appearance.spacing.normal
readonly property real itemRadius: Appearance.rounding.small
required property var modelData
height: root.cellHeight
width: root.cellWidth
StateLayer {
function onClicked(): void {
Wallpapers.setWallpaper(modelData.path);
}
anchors.bottomMargin: itemMargin
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
radius: itemRadius
}
CustomClippingRect {
id: image
@@ -53,8 +40,6 @@ GridView {
anchors.topMargin: itemMargin
antialiasing: true
color: DynamicColors.tPalette.m3surfaceContainer
layer.enabled: true
layer.smooth: true
radius: itemRadius
CachingImage {
@@ -100,29 +85,13 @@ GridView {
}
}
Timer {
id: fallbackTimer
property bool triggered: false
interval: 800
running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null
onTriggered: triggered = true
}
}
Rectangle {
anchors.bottomMargin: itemMargin
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
antialiasing: true
border.color: DynamicColors.palette.m3primary
border.width: isCurrent ? 2 : 0
color: "transparent"
radius: itemRadius - border.width
radius: itemRadius + 2
smooth: true
Behavior on border.width {
@@ -142,5 +111,30 @@ GridView {
visible: isCurrent
}
}
Timer {
id: fallbackTimer
property bool triggered: false
interval: 800
running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null
onTriggered: triggered = true
}
}
StateLayer {
function onClicked(): void {
Wallpapers.setWallpaper(modelData.path);
}
anchors.bottomMargin: itemMargin
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
radius: itemRadius
}
}
}
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -6,7 +6,7 @@ import QtQuick.Controls
import qs.Components
import qs.Config
import "../../scripts/fuzzysort.js" as Fuzzy
import "./SettingsIndex.mjs" as SettingsIndex
import "../../scripts/SettingsIndex.mjs" as SettingsIndex
Item {
id: root
@@ -53,11 +53,10 @@ Item {
Shortcut {
sequence: "/"
onActivated: searchField.forceActiveFocus()
}
Component.onCompleted: console.log(root.height)
ListModel {
id: resultsModel
}
+9 -30
View File
@@ -7,45 +7,24 @@ import qs.Helpers
Item {
id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities
implicitHeight: 0
anchors.topMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth
visible: height > 0
states: State {
name: "visible"
when: root.visibilities.settings
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
]
CustomClippingRect {
anchors.fill: parent
+2 -2
View File
@@ -14,8 +14,8 @@ Scope {
description: "Toggle launcher"
name: "toggle-launcher"
onPressed: root.launcherInterrupted = false
onReleased: {
onPressed: {
root.launcherInterrupted = false;
if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher;
+7 -5
View File
@@ -82,12 +82,14 @@ StackView {
CustomRect {
id: item
required property int index
required property QsMenuEntry modelData
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
implicitWidth: root.biggestWidth
radius: Appearance.rounding.smallest / 2
radius: Appearance.rounding.full
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
Loader {
id: children
@@ -201,18 +203,18 @@ StackView {
asynchronous: true
sourceComponent: Item {
implicitHeight: back.implicitHeight + 2 / 2
implicitHeight: 30
implicitWidth: back.implicitWidth
Item {
anchors.bottom: parent.bottom
implicitHeight: back.implicitHeight
implicitWidth: back.implicitWidth + 10
implicitHeight: 30
implicitWidth: root.biggestWidth
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.smallest / 2
radius: Appearance.rounding.full
StateLayer {
function onClicked(): void {
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent"
radius: 6
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
CustomRect {
id: indicator
+1
View File
@@ -1,5 +1,6 @@
import QtQuick.Layouts
import QtQuick
import QtQuick.VectorImage
import Quickshell
import Quickshell.Services.SystemTray
import qs.Modules
-30
View File
@@ -1,30 +0,0 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
readonly property date date: clock.date
readonly property string dateStr: format("ddd d MMM - hh:mm:ss")
property alias enabled: clock.enabled
readonly property string hourStr: timeComponents[0] ?? ""
readonly property int hours: clock.hours
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes
readonly property string secondStr: timeComponents[2] ?? ""
readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm:ss")
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
SystemClock {
id: clock
precision: SystemClock.Seconds
}
}
+5 -2
View File
@@ -54,6 +54,8 @@ CustomClippingRect {
anchors.centerIn: parent
contentHeight: childrenRect.height
contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth
spacing: Appearance.spacing.normal
@@ -65,10 +67,11 @@ CustomClippingRect {
required property var modelData
readonly property list<string> sections: modelData.update.split(" ")
anchors.left: parent.left
anchors.right: parent.right
// anchors.left: parent.left
// anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small
RowLayout {
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
radius: height / 2
radius: Appearance.rounding.full
RowLayout {
id: contentRow
+80
View File
@@ -0,0 +1,80 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
property Image current: one
property string source: Wallpapers.current
anchors.fill: parent
Component.onCompleted: {
if (source)
Qt.callLater(() => one.update());
}
onSourceChanged: {
if (!source) {
current = null;
} else if (current === one) {
two.update();
} else {
one.update();
}
}
Img {
id: one
}
Img {
id: two
}
component Img: Image {
id: img
function update(): void {
if (source === root.source) {
root.current = this;
} else {
source = root.source;
}
}
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: 0
retainWhileLoading: true
scale: Wallpapers.showPreview ? 1 : 0.8
sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
states: State {
name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
}
}
transitions: Transition {
Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
}
}
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
}
}
}
}
+56 -57
View File
@@ -1,5 +1,7 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Hyprland
import QtQuick
import qs.Components
import qs.Helpers
@@ -8,79 +10,76 @@ import qs.Config
Item {
id: root
property Image current: one
required property ShellScreen screen
property string source: Wallpapers.current
function refreshData(): void {
Hyprland.refreshMonitors();
const scale = Hyprland.monitorFor(root.screen).scale;
if (scale > 0 && img.resScale !== scale) {
img.resScale = scale;
img.sourceSize.width = root.screen.width * scale;
}
const displayData = Wallpapers.getCrop(root.screen.name);
const displayRect = Qt.rect(img.sourceSize.width * displayData.x, img.implicitHeight * displayData.y, img.sourceSize.width * displayData.width, img.implicitHeight * displayData.height);
img.anchors.fill = null;
img.zoom = displayData.zoom;
img.x = -(displayRect.x * displayData.zoom / img.resScale);
img.y = -(displayRect.y * displayData.zoom / img.resScale);
}
anchors.fill: parent
Component.onCompleted: {
if (source)
Qt.callLater(() => one.update());
}
onSourceChanged: {
if (!source) {
current = null;
} else if (current === one) {
two.update();
} else {
one.update();
}
}
Img {
id: one
}
Img {
id: two
}
component Img: CachingImage {
Image {
id: img
property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height)
property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0
property real windowRatio: root.width / Math.max(1, root.height)
property int displayH
property int displayW
property real resScale
property real zoom: 1.0
function update(): void {
if (path === root.source) {
root.current = this;
} else {
path = root.source;
}
}
anchors.fill: undefined
asynchronous: true
fillMode: Image.PreserveAspectCrop
height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height
opacity: 0
scale: Wallpapers.showPreview ? 1 : 0.8
width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width
x: isValid ? (root.width - width) * Config.background.alignX : 0
y: isValid ? (root.height - height) * Config.background.alignY : 0
height: implicitHeight * zoom / resScale
opacity: 1
retainWhileLoading: true
source: root.source
sourceSize.width: root.screen.width * resScale
width: implicitWidth * zoom / resScale
states: State {
name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
}
}
transitions: Transition {
Behavior on height {
Anim {
}
}
Behavior on width {
Anim {
}
}
Behavior on x {
Anim {
}
}
Behavior on y {
Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
}
}
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
if (img.status == Image.Ready) {
root.refreshData();
}
}
Connections {
function onAdapterUpdated(): void {
root.refreshData();
}
function onLoaded(): void {
root.refreshData();
}
target: Wallpapers.monitorCrops
}
}
}
+1
View File
@@ -30,6 +30,7 @@ Loader {
}
WallBackground {
screen: root.screen
}
Loader {
+2 -1
View File
@@ -79,7 +79,8 @@ Item {
}
onPressed: {
Hyprland.dispatch(`workspace ${button.modelData.name}`);
const ws = button.modelData.name;
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
}
}
}
+1
View File
@@ -12,6 +12,7 @@ qml_module(ZShell-blobs
qt_add_shaders(ZShell-blobs "blob_shaders"
BATCHABLE OPTIMIZED NOHLSL NOMSL
GLSL "300es,330"
PREFIX "/"
FILES
shaders/blob.frag
+7 -1
View File
@@ -2,6 +2,9 @@
#include <cstring>
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
"BlobMaterial packs excludeMask into a float slot via memcpy");
QSGMaterialType* BlobMaterial::type() const {
static QSGMaterialType s_type;
return &s_type;
@@ -82,8 +85,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
for (int i = 0; i < count; ++i) {
const auto& r = mat->m_rects[i];
const int base = 160 + i * 80;
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
float maskAsFloat;
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
const float d0[4] = { r.cx, r.cy, r.hw, r.hh };
const float d1[4] = { 0.0f, r.offsetX, r.offsetY, r.minEig };
const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig };
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
memcpy(buf->data() + base, d0, 16);
memcpy(buf->data() + base + 16, d1, 16);
+3
View File
@@ -14,6 +14,9 @@ struct BlobRectData {
float screenHalfX = 0, screenHalfY = 0;
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
float radius[4] = { 0, 0, 0, 0 };
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
// Used by the shader to skip smin between excluded pairs.
int excludeMask = 0;
};
class BlobMaterial : public QSGMaterial {
+33 -3
View File
@@ -72,11 +72,17 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome
// Accumulate sub-pixel drift so slow movements don't desync the shader
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
const auto dw = std::abs(newGeometry.width() - oldGeometry.width());
const auto dh = std::abs(newGeometry.height() - oldGeometry.height());
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) {
// Accumulate size delta across multiple frames so incremental size
// changes that are each below the threshold still trigger a dirty
// mark once their accumulated delta exceeds it.
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width());
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height());
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f ||
std::abs(m_pendingDw) > 0.5f || std::abs(m_pendingDh) > 0.5f) {
m_pendingDx = 0;
m_pendingDy = 0;
m_pendingDw = 0;
m_pendingDh = 0;
m_group->markShapeDirty(this);
}
}
@@ -149,6 +155,10 @@ void BlobShape::updatePolish() {
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH));
// Track shape pointers parallel to m_cachedRects for pairwise exclusion lookups
QVector<BlobShape*> rectShapes;
rectShapes.reserve(m_group->shapes().size());
for (BlobShape* other : m_group->shapes()) {
if (other->isInvertedRect())
continue;
@@ -210,12 +220,29 @@ void BlobShape::updatePolish() {
r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh;
m_cachedRects.append(r);
rectShapes.append(other);
}
}
if (isInvertedRect())
m_cachedMyIndex = -1;
// Compute pairwise exclude masks. Bit j in entry i is set iff rect i excludes rect j
// or rect j excludes rect i. The shader uses this to avoid smin between excluded pairs.
const auto cachedCount = m_cachedRects.size();
for (qsizetype i = 0; i < cachedCount; ++i) {
int mask = 0;
BlobShape* si = rectShapes[i];
for (qsizetype j = 0; j < cachedCount; ++j) {
if (j == i)
continue;
BlobShape* sj = rectShapes[j];
if (si->isExcluded(sj) || sj->isExcluded(si))
mask |= (1 << j);
}
m_cachedRects[i].excludeMask = mask;
}
// Cache inverted rect data
m_cachedHasInverted = false;
m_cachedInvertedRadius = 0;
@@ -270,6 +297,7 @@ void BlobShape::updatePolish() {
const auto rectCount = m_cachedRects.size();
for (qsizetype i = 0; i < rectCount; ++i) {
auto& ri = m_cachedRects[i];
const int riExcludeMask = ri.excludeMask;
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
@@ -280,6 +308,8 @@ void BlobShape::updatePolish() {
for (qsizetype j = 0; j < rectCount; ++j) {
if (j == i)
continue;
if (riExcludeMask & (1 << j))
continue;
const auto& rj = m_cachedRects[j];
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh)));
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
+4 -2
View File
@@ -84,8 +84,10 @@ QRectF m_localPaddedRect;
QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2;
float m_pendingDx = 0;
float m_pendingDy = 0;
bool m_cachedHasInverted = false;
float m_pendingDy = 0;
float m_pendingDw = 0;
float m_pendingDh = 0;
bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {};
+35 -4
View File
@@ -63,13 +63,17 @@ float smaxSharpA(float a, float b, float k) {
void main() {
vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH);
float mergedSdf = 1e10;
// Phase 1: compute per-rect SDF, track owner. We can't smin yet because
// excluded pairs need to skip the smooth blend, which requires pairwise pass
// below.
float dArr[16];
int owner = -2;
float minDist = 1e10;
for (int i = 0; i < rectCount; i++) {
vec4 rect = rectData[i * 5]; // cx, cy, hw, hh
vec4 props = rectData[i * 5 + 1]; // radius, offsetX, offsetY, minEig
vec4 props =
rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig
vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0
vec4 radii =
@@ -81,8 +85,10 @@ void main() {
// AABB early-out: skip rects far from this pixel
vec2 extent = sh.xy + vec2(smoothFactor * 1.5);
if (abs(pixel.x - center.x) > extent.x ||
abs(pixel.y - center.y) > extent.y)
abs(pixel.y - center.y) > extent.y) {
dArr[i] = 1e10;
continue;
}
// Apply pre-computed inverse deformation to the evaluation point
mat2 invDeform = mat2(invDm.xy, invDm.zw);
@@ -138,13 +144,38 @@ void main() {
d *= scale;
}
mergedSdf = smin(mergedSdf, d, smoothFactor);
dArr[i] = d;
if (d < smoothFactor && d < minDist) {
minDist = d;
owner = i;
}
}
// Phase 2: hard-min baseline over all rects.
float mergedSdf = 1e10;
for (int i = 0; i < rectCount; i++) {
mergedSdf = min(mergedSdf, dArr[i]);
}
// Phase 3: pair-wise smin contributions, skipping excluded pairs. Pair smin
// <= min, so taking the min over all non-excluded pair smins gives the
// smoothly-merged SDF.
for (int i = 0; i < rectCount; i++) {
if (dArr[i] >= 1e9)
continue;
int excludeMask = floatBitsToInt(rectData[i * 5 + 1].x);
for (int j = i + 1; j < rectCount; j++) {
if (dArr[j] >= 1e9)
continue;
if ((excludeMask & (1 << j)) != 0)
continue;
// smin only deviates from min within smoothFactor
if (abs(dArr[i] - dArr[j]) >= smoothFactor)
continue;
mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor));
}
}
if (hasInverted != 0) {
float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
float dInner =
+193 -26
View File
@@ -1,12 +1,170 @@
#include "hyprextras.hpp"
#include "hyprdevices.hpp"
#include <qdir.h>
#include <qjsonarray.h>
#include <qlocalsocket.h>
#include <qloggingcategory.h>
#include <qmetatype.h>
#include <qregularexpression.h>
#include <qvariant.h>
Q_LOGGING_CATEGORY(lcHypr, "ZShell.internal.hypr", QtInfoMsg)
namespace ZShell::internal::hypr {
namespace {
static QString luaEscapeString(const QString& s) {
QString out;
out.reserve(s.size() + 2);
out += QLatin1Char('"');
for (const QChar c : s) {
switch (c.unicode()) {
case '\\':
out += QLatin1String(R"(\\)");
break;
case '"':
out += QLatin1String(R"(\")");
break;
case '\n':
out += QLatin1String(R"(\n)");
break;
case '\r':
out += QLatin1String(R"(\r)");
break;
case '\t':
out += QLatin1String(R"(\t)");
break;
default:
out += c;
break;
}
}
out += QLatin1Char('"');
return out;
}
static QString luaValue(const QVariant& v);
static QString luaArray(const QVariantList& list) {
QStringList parts;
parts.reserve(list.size());
for (const auto& item : list) {
parts << luaValue(item);
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaArray(const QStringList& list) {
QStringList parts;
parts.reserve(list.size());
for (const auto& item : list) {
parts << luaEscapeString(item);
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaMapFromHash(const QVariantHash& hash) {
QStringList parts;
parts.reserve(hash.size());
for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaMap(const QVariantMap& map) {
QStringList parts;
parts.reserve(map.size());
for (auto it = map.cbegin(); it != map.cend(); ++it) {
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaValue(const QVariant& v) {
if (!v.isValid() || v.isNull()) {
return QLatin1String("nil");
}
switch (v.metaType().id()) {
case QMetaType::Bool:
return v.toBool() ? QLatin1String("true") : QLatin1String("false");
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Double:
return v.toString();
case QMetaType::QString:
return luaEscapeString(v.toString());
case QMetaType::QStringList:
return luaArray(v.toStringList());
case QMetaType::QVariantList:
return luaArray(v.toList());
case QMetaType::QVariantMap:
return luaMap(v.toMap());
case QMetaType::QVariantHash:
return luaMapFromHash(v.toHash());
default:
return luaEscapeString(v.toString());
}
}
static QString normalizeOptionPath(QString key) {
key = key.trimmed();
key.replace(QLatin1Char(':'), QLatin1Char('.'));
return key;
}
static QString buildHlConfigCall(const QString& key, const QVariant& value) {
const auto parts = normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts);
if (parts.isEmpty()) {
return {};
}
QString out;
out.reserve(32 + key.size() + value.toString().size());
out += QLatin1String("hl.config({ ");
for (int i = 0; i < parts.size(); ++i) {
out += parts.at(i);
out += QLatin1String(" = ");
if (i + 1 < parts.size()) {
out += QLatin1String("{ ");
}
}
out += luaValue(value);
for (int i = 0; i + 1 < parts.size(); ++i) {
out += QLatin1String(" }");
}
out += QLatin1String(" })");
return out;
}
} // namespace
HyprExtras::HyprExtras(QObject* parent)
: QObject(parent)
, m_requestSocket("")
@@ -16,24 +174,22 @@ HyprExtras::HyprExtras(QObject* parent)
, m_devices(new HyprDevices(this)) {
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
if (his.isEmpty()) {
qWarning()
<< "HyprExtras::HyprExtras: $HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
return;
}
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
if (!QDir(hyprDir).exists()) {
hyprDir = "/tmp/hypr/" + his;
hyprDir = QStringLiteral("/tmp/hypr/") + his;
if (!QDir(hyprDir).exists()) {
qWarning() << "HyprExtras::HyprExtras: Hyprland socket directory does not exist. Unable to connect to "
"Hyprland socket.";
qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket.";
return;
}
}
m_requestSocket = hyprDir + "/.socket.sock";
m_eventSocket = hyprDir + "/.socket2.sock";
m_requestSocket = hyprDir + QStringLiteral("/.socket.sock");
m_eventSocket = hyprDir + QStringLiteral("/.socket2.sock");
refreshOptions();
refreshDevices();
@@ -62,7 +218,7 @@ void HyprExtras::message(const QString& message) {
makeRequest(message, [](bool success, const QByteArray& res) {
if (!success) {
qWarning() << "HyprExtras::message: request error:" << QString::fromUtf8(res);
qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res);
}
});
}
@@ -72,9 +228,10 @@ void HyprExtras::batchMessage(const QStringList& messages) {
return;
}
makeRequest("[[BATCH]]" + messages.join(";"), [](bool success, const QByteArray& res) {
makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
[](bool success, const QByteArray& res) {
if (!success) {
qWarning() << "HyprExtras::batchMessage: request error:" << QString::fromUtf8(res);
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res);
}
});
}
@@ -84,16 +241,25 @@ void HyprExtras::applyOptions(const QVariantHash& options) {
return;
}
QString request = "[[BATCH]]";
QStringList calls;
calls.reserve(options.size());
for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
request += QString("keyword %1 %2;").arg(it.key(), it.value().toString());
const auto call = buildHlConfigCall(it.key(), it.value());
if (!call.isEmpty()) {
calls << call;
}
}
makeRequest(request, [this](bool success, const QByteArray& res) {
if (calls.isEmpty()) {
return;
}
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
if (success) {
refreshOptions();
} else {
qWarning() << "HyprExtras::applyOptions: request error" << QString::fromUtf8(res);
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res);
}
});
}
@@ -103,7 +269,7 @@ void HyprExtras::refreshOptions() {
m_optionsRefresh->close();
}
m_optionsRefresh = makeRequestJson("descriptions", [this](bool success, const QJsonDocument& response) {
m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) {
m_optionsRefresh.reset();
if (!success) {
return;
@@ -114,8 +280,9 @@ void HyprExtras::refreshOptions() {
for (const auto& o : std::as_const(options)) {
const auto obj = o.toObject();
const auto key = obj.value("value").toString();
const auto value = obj.value("data").toObject().value("current").toVariant();
const auto key = obj.value(QStringLiteral("value")).toString();
const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant();
if (m_options.value(key) != value) {
dirty = true;
m_options.insert(key, value);
@@ -133,7 +300,7 @@ void HyprExtras::refreshDevices() {
m_devicesRefresh->close();
}
m_devicesRefresh = makeRequestJson("devices", [this](bool success, const QJsonDocument& response) {
m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) {
m_devicesRefresh.reset();
if (success) {
m_devices->updateLastIpcObject(response.object());
@@ -143,15 +310,15 @@ void HyprExtras::refreshDevices() {
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
if (!m_socketValid) {
qWarning() << "HyprExtras::socketError: unable to connect to Hyprland event socket:" << error;
qCWarning(lcHypr) << "socketError: unable to connect to Hyprland event socket:" << error;
} else {
qWarning() << "HyprExtras::socketError: Hyprland event socket error:" << error;
qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error;
}
}
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
if (state == QLocalSocket::UnconnectedState && m_socketValid) {
qWarning() << "HyprExtras::socketStateChanged: Hyprland event socket disconnected.";
qCWarning(lcHypr) << "socketStateChanged: Hyprland event socket disconnected.";
}
m_socketValid = state == QLocalSocket::ConnectedState;
@@ -163,23 +330,23 @@ void HyprExtras::readEvent() {
if (rawEvent.isEmpty()) {
break;
}
rawEvent.truncate(rawEvent.length() - 1); // Remove trailing \n
rawEvent.truncate(rawEvent.length() - 1);
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
handleEvent(QString::fromUtf8(event));
}
}
void HyprExtras::handleEvent(const QString& event) {
if (event == "configreloaded") {
if (event == QStringLiteral("configreloaded")) {
refreshOptions();
} else if (event == "activelayout") {
} else if (event == QStringLiteral("activelayout")) {
refreshDevices();
}
}
HyprExtras::SocketPtr HyprExtras::makeRequestJson(
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
return makeRequest("j/" + request, [callback](bool success, const QByteArray& response) {
return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) {
callback(success, QJsonDocument::fromJson(response));
});
}
@@ -204,7 +371,7 @@ HyprExtras::SocketPtr HyprExtras::makeRequest(
});
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) {
qWarning() << "HyprExtras::makeRequest: error making request:" << err << "| request:" << request;
qCWarning(lcHypr) << "makeRequest: error making request:" << err << "| request:" << request;
callback(false, {});
socket->close();
});
+34 -30
View File
@@ -1,56 +1,60 @@
#pragma once
#include "hyprdevices.hpp"
#include <qlocalsocket.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qsharedpointer.h>
#include <qvariant.h>
namespace ZShell::internal::hypr {
class HyprExtras : public QObject {
Q_OBJECT
QML_ELEMENT
class HyprDevices;
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
class HyprExtras : public QObject {
Q_OBJECT
QML_ELEMENT
Q_MOC_INCLUDE("hyprdevices.hpp")
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
public:
explicit HyprExtras(QObject* parent = nullptr);
explicit HyprExtras(QObject* parent = nullptr);
[[nodiscard]] QVariantHash options() const;
[[nodiscard]] HyprDevices* devices() const;
[[nodiscard]] QVariantHash options() const;
[[nodiscard]] HyprDevices* devices() const;
Q_INVOKABLE void message(const QString& message);
Q_INVOKABLE void batchMessage(const QStringList& messages);
Q_INVOKABLE void applyOptions(const QVariantHash& options);
Q_INVOKABLE void message(const QString& message);
Q_INVOKABLE void batchMessage(const QStringList& messages);
Q_INVOKABLE void applyOptions(const QVariantHash& options);
Q_INVOKABLE void refreshOptions();
Q_INVOKABLE void refreshDevices();
Q_INVOKABLE void refreshOptions();
Q_INVOKABLE void refreshDevices();
signals:
void optionsChanged();
void optionsChanged();
private:
using SocketPtr = QSharedPointer<QLocalSocket>;
using SocketPtr = QSharedPointer<QLocalSocket>;
QString m_requestSocket;
QString m_eventSocket;
QLocalSocket* m_socket;
bool m_socketValid;
QString m_requestSocket;
QString m_eventSocket;
QLocalSocket* m_socket;
bool m_socketValid;
QVariantHash m_options;
HyprDevices* const m_devices;
QVariantHash m_options;
HyprDevices* const m_devices;
SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh;
SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh;
void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state);
void readEvent();
void handleEvent(const QString& event);
void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state);
void readEvent();
void handleEvent(const QString& event);
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback);
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback);
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
};
} // namespace ZShell::internal::hypr
+2
View File
@@ -74,6 +74,8 @@ Defaults from `CMakeLists.txt`:
### Nix Flake
_Note that Nix is not actively developed at this point. Things may be broken. Feel free to suggest fixes in a PR_
The flake exposes:
- `packages.<system>.zshell`
+1 -1
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
import typer
from zshell.subcommands import shell, scheme, screenshot, wallpaper, preset
from zshell.subcommands import shell, scheme, screenshot, wallpaper
app = typer.Typer()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,14 +9,8 @@ _data = {
"variants": {
"type": "multi",
"defaults": {
"dark": {
"m3flavor": "mocha",
"m3accent": "mauve"
},
"light": {
"m3flavor": "latte",
"m3accent": "mauve"
}
"dark": {"m3flavor": "mocha", "m3accent": "mauve"},
"light": {"m3flavor": "latte", "m3accent": "mauve"},
},
"flavors": [
{
@@ -35,8 +29,8 @@ _data = {
"m3surfaceContainerHighest": "#dce0e8",
"m3error": "#d20f39",
"m3warning": "#fe640b",
"m3info": "#1e66f5"
}
"m3info": "#1e66f5",
},
},
{
"id": "frappe",
@@ -54,8 +48,8 @@ _data = {
"m3surfaceContainerHighest": "#232634",
"m3error": "#e78284",
"m3warning": "#ef9f76",
"m3info": "#8caaee"
}
"m3info": "#8caaee",
},
},
{
"id": "macchiato",
@@ -73,8 +67,8 @@ _data = {
"m3surfaceContainerHighest": "#181926",
"m3error": "#ed8796",
"m3warning": "#f5a97f",
"m3info": "#8aadf4"
}
"m3info": "#8aadf4",
},
},
{
"id": "mocha",
@@ -92,9 +86,9 @@ _data = {
"m3surfaceContainerHighest": "#11111b",
"m3error": "#f38ba8",
"m3warning": "#fab387",
"m3info": "#89b4fa"
}
}
"m3info": "#89b4fa",
},
},
],
"accents": [
{
@@ -105,29 +99,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#e1a99d",
"m3secondary": "#d8c7c4",
"m3surfaceTint": "#e1a99d"
"m3surfaceTint": "#e1a99d",
},
"frappe": {
"m3primary": "#f2d5cf",
"m3primaryText": "#303446",
"m3primaryContainer": "#b8a5a6",
"m3secondary": "#a2748b",
"m3surfaceTint": "#b8a5a6"
"m3surfaceTint": "#b8a5a6",
},
"macchiato": {
"m3primary": "#f4dbd6",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b6a6a7",
"m3secondary": "#9f6f8d",
"m3surfaceTint": "#b6a6a7"
"m3surfaceTint": "#b6a6a7",
},
"mocha": {
"m3primary": "#f5e0dc",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b5a6a8",
"m3secondary": "#9d6d87",
"m3surfaceTint": "#b5a6a8"
}
"m3surfaceTint": "#b5a6a8",
},
},
{
"id": "flamingo",
@@ -137,29 +131,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#e29c9d",
"m3secondary": "#d7c3c4",
"m3surfaceTint": "#e29c9d"
"m3surfaceTint": "#e29c9d",
},
"frappe": {
"m3primary": "#eebebe",
"m3primaryText": "#303446",
"m3primaryContainer": "#b5949a",
"m3secondary": "#9d6b80",
"m3surfaceTint": "#b5949a"
"m3surfaceTint": "#b5949a",
},
"macchiato": {
"m3primary": "#f0c6c6",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b3979c",
"m3secondary": "#996780",
"m3surfaceTint": "#b3979c"
"m3surfaceTint": "#b3979c",
},
"mocha": {
"m3primary": "#f2cdcd",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b3999e",
"m3secondary": "#98667c",
"m3surfaceTint": "#b3999e"
}
"m3surfaceTint": "#b3999e",
},
},
{
"id": "pink",
@@ -169,29 +163,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#eb9bd7",
"m3secondary": "#d9c7d5",
"m3surfaceTint": "#eb9bd7"
"m3surfaceTint": "#eb9bd7",
},
"frappe": {
"m3primary": "#f4b8e4",
"m3primaryText": "#303446",
"m3primaryContainer": "#b990b5",
"m3secondary": "#996e9e",
"m3surfaceTint": "#b990b5"
"m3surfaceTint": "#b990b5",
},
"macchiato": {
"m3primary": "#f5bde6",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b791b2",
"m3secondary": "#95689a",
"m3surfaceTint": "#b791b2"
"m3surfaceTint": "#b791b2",
},
"mocha": {
"m3primary": "#f5c2e7",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b591b0",
"m3secondary": "#966597",
"m3surfaceTint": "#b591b0"
}
"m3surfaceTint": "#b591b0",
},
},
{
"id": "mauve",
@@ -201,29 +195,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#a670f1",
"m3secondary": "#c2b8d0",
"m3surfaceTint": "#a670f1"
"m3surfaceTint": "#a670f1",
},
"frappe": {
"m3primary": "#ca9ee6",
"m3primaryText": "#303446",
"m3primaryContainer": "#9c7eb6",
"m3secondary": "#7d6799",
"m3surfaceTint": "#9c7eb6"
"m3surfaceTint": "#9c7eb6",
},
"macchiato": {
"m3primary": "#c6a0f6",
"m3primaryText": "#24273a",
"m3primaryContainer": "#967cbe",
"m3secondary": "#766597",
"m3surfaceTint": "#967cbe"
"m3surfaceTint": "#967cbe",
},
"mocha": {
"m3primary": "#cba6f7",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#977ebb",
"m3secondary": "#756294",
"m3surfaceTint": "#977ebb"
}
"m3surfaceTint": "#977ebb",
},
},
{
"id": "red",
@@ -233,29 +227,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#da5371",
"m3secondary": "#c0a0a8",
"m3surfaceTint": "#da5371"
"m3surfaceTint": "#da5371",
},
"frappe": {
"m3primary": "#e78284",
"m3primaryText": "#303446",
"m3primaryContainer": "#b06a72",
"m3secondary": "#8b5d66",
"m3surfaceTint": "#b06a72"
"m3surfaceTint": "#b06a72",
},
"macchiato": {
"m3primary": "#ed8796",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b16b7a",
"m3secondary": "#865a69",
"m3surfaceTint": "#b16b7a"
"m3surfaceTint": "#b16b7a",
},
"mocha": {
"m3primary": "#f38ba8",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b46b84",
"m3secondary": "#85596b",
"m3surfaceTint": "#b46b84"
}
"m3surfaceTint": "#b46b84",
},
},
{
"id": "maroon",
@@ -265,29 +259,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#e87883",
"m3secondary": "#cfb7ba",
"m3surfaceTint": "#e87883"
"m3surfaceTint": "#e87883",
},
"frappe": {
"m3primary": "#ea999c",
"m3primaryText": "#303446",
"m3primaryContainer": "#b27a83",
"m3secondary": "#92626f",
"m3surfaceTint": "#b27a83"
"m3surfaceTint": "#b27a83",
},
"macchiato": {
"m3primary": "#ee99a0",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b27781",
"m3secondary": "#8c5e6c",
"m3surfaceTint": "#b27781"
"m3surfaceTint": "#b27781",
},
"mocha": {
"m3primary": "#eba0ac",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#ae7987",
"m3secondary": "#895b6c",
"m3surfaceTint": "#ae7987"
}
"m3surfaceTint": "#ae7987",
},
},
{
"id": "peach",
@@ -297,29 +291,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#f98e51",
"m3secondary": "#c9b7ad",
"m3surfaceTint": "#f98e51"
"m3surfaceTint": "#f98e51",
},
"frappe": {
"m3primary": "#ef9f76",
"m3primaryText": "#303446",
"m3primaryContainer": "#b67f68",
"m3secondary": "#8f6a5f",
"m3surfaceTint": "#b67f68"
"m3surfaceTint": "#b67f68",
},
"macchiato": {
"m3primary": "#f5a97f",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b7836a",
"m3secondary": "#8c695e",
"m3surfaceTint": "#b7836a"
"m3surfaceTint": "#b7836a",
},
"mocha": {
"m3primary": "#fab387",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b8876d",
"m3secondary": "#8b6a5d",
"m3surfaceTint": "#b8876d"
}
"m3surfaceTint": "#b8876d",
},
},
{
"id": "yellow",
@@ -329,29 +323,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#e4ac5d",
"m3secondary": "#c6baaa",
"m3surfaceTint": "#e4ac5d"
"m3surfaceTint": "#e4ac5d",
},
"frappe": {
"m3primary": "#e5c890",
"m3primaryText": "#303446",
"m3primaryContainer": "#af9b7a",
"m3secondary": "#948062",
"m3surfaceTint": "#af9b7a"
"m3surfaceTint": "#af9b7a",
},
"macchiato": {
"m3primary": "#eed49f",
"m3primaryText": "#24273a",
"m3primaryContainer": "#b2a181",
"m3secondary": "#947e62",
"m3surfaceTint": "#b2a181"
"m3surfaceTint": "#b2a181",
},
"mocha": {
"m3primary": "#f9e2af",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b8a889",
"m3secondary": "#978265",
"m3surfaceTint": "#b8a889"
}
"m3surfaceTint": "#b8a889",
},
},
{
"id": "green",
@@ -361,29 +355,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#74b867",
"m3secondary": "#9fbd9b",
"m3surfaceTint": "#74b867"
"m3surfaceTint": "#74b867",
},
"frappe": {
"m3primary": "#a6d189",
"m3primaryText": "#303446",
"m3primaryContainer": "#83a275",
"m3secondary": "#648e5e",
"m3surfaceTint": "#83a275"
"m3surfaceTint": "#83a275",
},
"macchiato": {
"m3primary": "#a6da95",
"m3primaryText": "#24273a",
"m3primaryContainer": "#80a57a",
"m3secondary": "#5c8a61",
"m3surfaceTint": "#80a57a"
"m3surfaceTint": "#80a57a",
},
"mocha": {
"m3primary": "#a6e3a1",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#7ea87f",
"m3secondary": "#5b8964",
"m3surfaceTint": "#7ea87f"
}
"m3surfaceTint": "#7ea87f",
},
},
{
"id": "teal",
@@ -393,29 +387,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#57aeb4",
"m3secondary": "#93b4b7",
"m3surfaceTint": "#57aeb4"
"m3surfaceTint": "#57aeb4",
},
"frappe": {
"m3primary": "#81c8be",
"m3primaryText": "#303446",
"m3primaryContainer": "#699b9a",
"m3secondary": "#588084",
"m3surfaceTint": "#699b9a"
"m3surfaceTint": "#699b9a",
},
"macchiato": {
"m3primary": "#8bd5ca",
"m3primaryText": "#24273a",
"m3primaryContainer": "#6da29f",
"m3secondary": "#577e83",
"m3surfaceTint": "#6da29f"
"m3surfaceTint": "#6da29f",
},
"mocha": {
"m3primary": "#94e2d5",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#71a8a4",
"m3secondary": "#588284",
"m3surfaceTint": "#71a8a4"
}
"m3surfaceTint": "#71a8a4",
},
},
{
"id": "sky",
@@ -425,29 +419,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#4abcea",
"m3secondary": "#a4b9c2",
"m3surfaceTint": "#4abcea"
"m3surfaceTint": "#4abcea",
},
"frappe": {
"m3primary": "#99d1db",
"m3primaryText": "#303446",
"m3primaryContainer": "#79a2af",
"m3secondary": "#628494",
"m3surfaceTint": "#79a2af"
"m3surfaceTint": "#79a2af",
},
"macchiato": {
"m3primary": "#91d7e3",
"m3primaryText": "#24273a",
"m3primaryContainer": "#71a3b0",
"m3secondary": "#5e7e8c",
"m3surfaceTint": "#71a3b0"
"m3surfaceTint": "#71a3b0",
},
"mocha": {
"m3primary": "#89dceb",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#69a3b3",
"m3secondary": "#5a7b88",
"m3surfaceTint": "#69a3b3"
}
"m3surfaceTint": "#69a3b3",
},
},
{
"id": "sapphire",
@@ -457,29 +451,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#5db8c8",
"m3secondary": "#9eb9be",
"m3surfaceTint": "#5db8c8"
"m3surfaceTint": "#5db8c8",
},
"frappe": {
"m3primary": "#85c1dc",
"m3primaryText": "#303446",
"m3primaryContainer": "#6b96af",
"m3secondary": "#5e7b8e",
"m3surfaceTint": "#6b96af"
"m3surfaceTint": "#6b96af",
},
"macchiato": {
"m3primary": "#7dc4e4",
"m3primaryText": "#24273a",
"m3primaryContainer": "#6396b1",
"m3secondary": "#5a7486",
"m3surfaceTint": "#6396b1"
"m3surfaceTint": "#6396b1",
},
"mocha": {
"m3primary": "#74c7ec",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#5a95b4",
"m3secondary": "#567080",
"m3surfaceTint": "#5a95b4"
}
"m3surfaceTint": "#5a95b4",
},
},
{
"id": "blue",
@@ -489,29 +483,29 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#5c90f5",
"m3secondary": "#b1bacb",
"m3surfaceTint": "#5c90f5"
"m3surfaceTint": "#5c90f5",
},
"frappe": {
"m3primary": "#8caaee",
"m3primaryText": "#303446",
"m3primaryContainer": "#7086bc",
"m3secondary": "#637195",
"m3surfaceTint": "#7086bc"
"m3surfaceTint": "#7086bc",
},
"macchiato": {
"m3primary": "#8aadf4",
"m3primaryText": "#24273a",
"m3primaryContainer": "#6c85bc",
"m3secondary": "#5f6d8f",
"m3surfaceTint": "#6c85bc"
"m3surfaceTint": "#6c85bc",
},
"mocha": {
"m3primary": "#89b4fa",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#6987bd",
"m3secondary": "#5d6c8b",
"m3surfaceTint": "#6987bd"
}
"m3surfaceTint": "#6987bd",
},
},
{
"id": "lavender",
@@ -521,30 +515,30 @@ _data = {
"m3primaryText": "#eff1f5",
"m3primaryContainer": "#97a7fb",
"m3secondary": "#cdcfdd",
"m3surfaceTint": "#97a7fb"
"m3surfaceTint": "#97a7fb",
},
"frappe": {
"m3primary": "#babbf1",
"m3primaryText": "#303446",
"m3primaryContainer": "#9192be",
"m3secondary": "#7175a1",
"m3surfaceTint": "#9192be"
"m3surfaceTint": "#9192be",
},
"macchiato": {
"m3primary": "#b7bdf8",
"m3primaryText": "#24273a",
"m3primaryContainer": "#8b91bf",
"m3secondary": "#6b709d",
"m3surfaceTint": "#8b91bf"
"m3surfaceTint": "#8b91bf",
},
"mocha": {
"m3primary": "#b4befe",
"m3primaryText": "#1e1e2e",
"m3primaryContainer": "#878ec0",
"m3secondary": "#676d99",
"m3surfaceTint": "#878ec0"
}
}
]
}
"m3surfaceTint": "#878ec0",
},
},
],
},
}
+12 -10
View File
@@ -1,11 +1,13 @@
import json
import typer
from zshell.assets.schemes.catppuccin import catppuccin
# import json
# import typer
# from zshell.assets.schemes.catppuccin import catppuccin
#
# app = typer.Typer()
#
# SCHEMES = catppuccin.variants.flavors
#
#
# @app.command()
# def set():
app = typer.Typer()
SCHEMES = catppuccin.variants.flavors
@app.command()
def set():
# TODO: Currently unsused
+15 -13
View File
@@ -1,14 +1,16 @@
import typer
import subprocess
# import typer
# import subprocess
#
# from typing import Optional
#
# app = typer.Typer()
#
# RECORDER = "gpu-screen-recorder"
# HOME = str(os.getenv("HOME"))
# CONFIG = Path(HOME + "/.config/zshell/config.json")
#
#
# @app.command()
# def start():
from typing import Optional
app = typer.Typer()
RECORDER = "gpu-screen-recorder"
HOME = str(os.getenv("HOME"))
CONFIG = Path(HOME + "/.config/zshell/config.json")
@app.command()
def start():
# TODO: Currently unused
+28 -32
View File
@@ -23,64 +23,71 @@ app = typer.Typer()
@app.command()
def generate(
# image inputs (optional - used for image mode)
image_path: Optional[Path] = typer.Option(
None, help="Path to source image. Required for image mode."),
image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."),
scheme: Optional[str] = typer.Option(
None, help="Color scheme algorithm to use for image mode. Ignored in preset mode."),
None, help="Color scheme algorithm to use for image mode. Ignored in preset mode."
),
# preset inputs (optional - used for preset mode)
preset: Optional[str] = typer.Option(
None, help="Name of a premade scheme in this format: <preset_name>:<preset_flavor>"),
mode: Optional[str] = typer.Option(
None, help="Mode of the preset scheme (dark or light)."),
None, help="Name of a premade scheme in this format: <preset_name>:<preset_flavor>"
),
mode: Optional[str] = typer.Option(None, help="Mode of the preset scheme (dark or light)."),
):
HOME = str(os.getenv("HOME"))
OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json")
SEQ_STATE = Path(HOME + "/.local/state/zshell/sequences.txt")
THUMB_PATH = Path(HOME +
"/.cache/zshell/imagecache/thumbnail.jpg")
WALL_DIR_PATH = Path(HOME +
"/.local/state/zshell/wallpaper_path.json")
THUMB_PATH = Path(HOME + "/.cache/zshell/imagecache/thumbnail.jpg")
WALL_DIR_PATH = Path(HOME + "/.local/state/zshell/wallpaper_path.json")
TEMPLATE_DIR = Path(HOME + "/.config/zshell/templates")
WALL_PATH = Path()
CONFIG = Path(HOME + "/.config/zshell/config.json")
if preset is not None and image_path is not None:
raise typer.BadParameter(
"Use either --image-path or --preset, not both.")
raise typer.BadParameter("Use either --image-path or --preset, not both.")
def get_scheme_class(scheme_name: str):
match scheme_name:
case "fruit-salad":
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad
return SchemeFruitSalad
case "expressive":
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive
return SchemeExpressive
case "monochrome":
from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome
return SchemeMonochrome
case "rainbow":
from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow
return SchemeRainbow
case "tonal-spot":
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
return SchemeTonalSpot
case "neutral":
from materialyoucolor.scheme.scheme_neutral import SchemeNeutral
return SchemeNeutral
case "fidelity":
from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity
return SchemeFidelity
case "content":
from materialyoucolor.scheme.scheme_content import SchemeContent
return SchemeContent
case "vibrant":
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
return SchemeVibrant
case _:
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad
return SchemeFruitSalad
def hex_to_hct(hex_color: str) -> Hct:
@@ -163,16 +170,11 @@ def generate(
def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct:
diff = difference_degrees(from_hct.hue, to_hct.hue)
rotation = min(diff * 0.8, 100)
output_hue = sanitize_degrees_double(
from_hct.hue
+ rotation * rotation_direction(from_hct.hue, to_hct.hue)
)
output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue))
tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost)))
return Hct.from_hct(output_hue, from_hct.chroma, tone)
def terminal_palette(
colors: dict[str, str], mode: str, variant: str
) -> dict[str, str]:
def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]:
light = mode.lower() == "light"
key_hex = (
@@ -307,7 +309,7 @@ def generate(
"seed": seed.to_int(),
"flavor": flavor,
"variant": variant,
"colors": colors
"colors": colors,
}
for k, v in colors.items():
@@ -349,7 +351,7 @@ def generate(
def tmux_wrap_sequences(seq: str) -> str:
ESC = "\x1b"
return f"{ESC}Ptmux;{seq.replace(ESC, ESC+ESC)}{ESC}\\"
return f"{ESC}Ptmux;{seq.replace(ESC, ESC + ESC)}{ESC}\\"
def parse_output_directive(first_line: str) -> Optional[Path]:
s = first_line.strip()
@@ -406,8 +408,7 @@ def generate(
template = env.from_string(body)
text = template.render(**context)
except Exception as e:
raise RuntimeError(
f"Template render failed for '{rel}': {e}") from e
raise RuntimeError(f"Template render failed for '{rel}': {e}") from e
out_path.write_text(text, encoding="utf-8")
@@ -435,18 +436,13 @@ def generate(
try:
return PRESETS[name].primary
except KeyError:
raise typer.BadParameter(
f"Preset '{name}' not found. Available presets: {', '.join(PRESETS.keys())}")
raise typer.BadParameter(f"Preset '{name}' not found. Available presets: {', '.join(PRESETS.keys())}")
def generate_color_scheme(seed: Hct, mode: str, scheme_class) -> dict[str, str]:
is_dark = mode.lower() == "dark"
scheme = scheme_class(
seed,
is_dark,
0.0
)
scheme = scheme_class(seed, is_dark, 0.0)
color_dict = {}
for color in vars(MaterialDynamicColors).keys():
@@ -499,7 +495,7 @@ def generate(
"mode": effective_mode,
"variant": scheme,
"colors": colors,
"seed": seed.to_int()
"seed": seed.to_int(),
}
if TEMPLATE_DIR is not None:
@@ -511,7 +507,7 @@ def generate(
wallpaper_path=wp,
name=name,
flavor=flavor,
variant=scheme
variant=scheme,
)
rendered = render_all_templates(
+2 -4
View File
@@ -8,11 +8,9 @@ app = typer.Typer()
@app.command()
def start():
subprocess.run(args + ["ipc"] + ["call"] +
["picker"] + ["open"], check=True)
subprocess.run(args + ["ipc"] + ["call"] + ["picker"] + ["open"], check=True)
@app.command()
def start_freeze():
subprocess.run(args + ["ipc"] + ["call"] +
["picker"] + ["openFreeze"], check=True)
subprocess.run(args + ["ipc"] + ["call"] + ["picker"] + ["openFreeze"], check=True)
+1 -2
View File
@@ -33,5 +33,4 @@ def lock():
@app.command()
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
subprocess.run(args + ["ipc"] + ["call"] + [target] +
[method] + method_args, check=True)
subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + method_args, check=True)
+4 -5
View File
@@ -12,8 +12,7 @@ app = typer.Typer()
@app.command()
def set(wallpaper: Path):
subprocess.run(args + ["ipc"] + ["call"] +
["wallpaper"] + ["set"] + [wallpaper], check=True)
subprocess.run(args + ["ipc"] + ["call"] + ["wallpaper"] + ["set"] + [wallpaper], check=True)
@app.command()
@@ -26,15 +25,15 @@ def lockscreen(
Path,
typer.Option(),
],
blur_amount: int = 20
blur_amount: int = 20,
):
img = Image.open(input_image)
size = img.size
if (blur_amount == 0):
if blur_amount == 0:
img.save(output_path, "PNG")
return
if (size[0] < 3840 or size[1] < 2160):
if size[0] < 3840 or size[1] < 2160:
img = img.resize((size[0] // 2, size[1] // 2), Image.NEAREST)
else:
img = img.resize((size[0] // 4, size[1] // 4), Image.NEAREST)
+16
View File
@@ -0,0 +1,16 @@
export default [
{
ignores: ["scripts/fzf.js", "scripts/fuzzysort.js"],
},
{
files: ["**/*.{js,jsx,ts,tsx,mjs,cjs}"],
languageOptions: {
ecmaVersion: 2021,
sourceType: "module",
},
linterOptions: {
reportUnusedDisableDirectives: true,
},
rules: {},
},
];
File diff suppressed because it is too large Load Diff
+1100
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "zshell-img-tools"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "zshell-img-tools"
path = "src/main.rs"
[dependencies]
image = { version = "0.25", features = ["png"] }
tiny-skia = "0.11"
serde = { version = "1", features = ["derive"] }
anyhow = "1"
serde_json = "1.0.149"
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = true
[profile.dev]
opt-level = 0
[profile.dev.package."*"]
opt-level = 3
+6
View File
@@ -0,0 +1,6 @@
# What_That_Claude_DO?
What That Claude Do? (WTCD)
A repository of random things I ask Claude to do for me.
In this case it is creating a screenshot tool

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