77 Commits

Author SHA1 Message Date
zach 33746fca04 Initial commit for submenu popouts, unfinished 2026-05-20 14:07:38 +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
69 changed files with 2950 additions and 2506 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
+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"
}
+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 color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitHeight: label.implicitHeight + padding * 2 implicitHeight: label.implicitHeight + padding * 2
implicitWidth: implicitHeight 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 { Behavior on radius {
Anim { Anim {
id: radiusAnim id: radiusAnim
} }
} }
+4
View File
@@ -7,4 +7,8 @@ JsonObject {
property real alignX: 0.5 property real alignX: 0.5
property real alignY: 0.5 property real alignY: 0.5
property real zoom: 1.0 property real zoom: 1.0
property real sourceClipX: 0
property real sourceClipY: 0
property real sourceClipW: 0
property real sourceClipH: 0
} }
+1
View File
@@ -58,6 +58,7 @@ JsonObject {
property Popouts popouts: Popouts { property Popouts popouts: Popouts {
} }
property int rounding: 8 property int rounding: 8
property int smoothing: 32
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool activeWindow: true
+6
View File
@@ -83,6 +83,10 @@ Singleton {
wallFadeDuration: background.wallFadeDuration, wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled, enabled: background.enabled,
alignX: background.alignX, alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY, alignY: background.alignY,
zoom: background.zoom zoom: background.zoom
}; };
@@ -94,6 +98,7 @@ Singleton {
hideWhenNotif: barConfig.hideWhenNotif, hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding, rounding: barConfig.rounding,
border: barConfig.border, border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height, height: barConfig.height,
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
@@ -236,6 +241,7 @@ Singleton {
return { return {
recolorLogo: lock.recolorLogo, recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint, enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
maxFprintTries: lock.maxFprintTries, maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount, blurAmount: lock.blurAmount,
sizes: { sizes: {
+35 -4
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Hyprland
import QtQuick import QtQuick
import ZShell import ZShell
import qs.Helpers import qs.Helpers
@@ -79,10 +80,32 @@ Singleton {
} }
function reloadHyprRules(): void { function reloadHyprRules(): void {
const barStr = "keyword layerrule %1 %2, match:namespace ZShell-Bar"; const blur = transparency.enabled ? 1 : 0;
const authStr = "keyword layerrule %1 %2, match:namespace ZShell-Auth"; const alpha = transparency.base - 0.03;
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 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 { function setMode(mode: string): void {
@@ -93,6 +116,14 @@ Singleton {
Component.onCompleted: debounceTimer.triggered() Component.onCompleted: debounceTimer.triggered()
Connections {
function onUsingLuaChanged(): void {
root.reloadHyprRules();
}
target: Hyprland
}
Connections { Connections {
function onConfigReloaded(): void { function onConfigReloaded(): void {
root.reloadHyprRules(); root.reloadHyprRules();
+1
View File
@@ -5,6 +5,7 @@ JsonObject {
property bool enableFprint: true property bool enableFprint: true
property int maxFprintTries: 3 property int maxFprintTries: 3
property bool recolorLogo: false property bool recolorLogo: false
property bool showNotifContent: false
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
+23 -174
View File
@@ -3,184 +3,33 @@ import QtQuick
Canvas { Canvas {
id: root 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 color penColor: "white"
property real penWidth: 4 property real penWidth: 4
property var pendingSegments: [] property var points: []
property bool strokeActive: false
property var strokes: []
function appendPoint(x, y) { function clear(): void {
if (!strokeActive || strokes.length === 0) var ctx = getContext('2d');
root.points = [];
ctx.reset();
root.requestPaint();
}
renderStrategy: Canvas.Cooperative
onPaint: {
if (points.length < 2)
return; return;
const dx = x - lastPoint.x; var ctx = root.getContext('2d');
const dy = y - lastPoint.y; ctx.save();
ctx.lineWidth = root.penWidth;
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance)) ctx.strokeStyle = root.penColor;
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");
ctx.lineCap = "round"; ctx.lineCap = "round";
ctx.lineJoin = "round"; ctx.beginPath();
ctx.lineWidth = penWidth; ctx.moveTo(points[0].x, points[0].y);
ctx.strokeStyle = penColor; for (var i = 1; i < points.length; i++)
ctx.fillStyle = penColor; ctx.lineTo(points[i].x, points[i].y);
ctx.stroke();
if (fullRepaintPending) { points = points.slice(points.length - 2);
fullRepaintPending = false; ctx.restore();
replayAll(ctx);
pendingSegments = [];
return;
}
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 x = event.x;
const y = event.y; const y = event.y;
if (event.buttons & Qt.LeftButton) if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.appendPoint(x, y); root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
}
if (root.inLeftPanel(root.popout, x, y)) { if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2; root.z = -2;
@@ -44,7 +46,8 @@ CustomMouseArea {
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false; root.panels.drawing.expanded = false;
root.drawing.beginStroke(x, y); root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return; return;
} }
@@ -52,7 +55,6 @@ CustomMouseArea {
root.drawing.clear(); root.drawing.clear();
} }
onReleased: { onReleased: {
if (root.visibilities.isDrawing) root.drawing.points = [];
root.drawing.endStroke();
} }
} }
+15
View File
@@ -12,22 +12,37 @@ Scope {
required property ShellScreen screen required property ShellScreen screen
ExclusionZone { ExclusionZone {
id: top
anchors.top: true anchors.top: true
exclusiveZone: root.bar.exclusiveZone exclusiveZone: root.bar.exclusiveZone
} }
ExclusionZone { ExclusionZone {
id: left
anchors.left: true anchors.left: true
} }
ExclusionZone { ExclusionZone {
id: right
anchors.right: true anchors.right: true
} }
ExclusionZone { ExclusionZone {
id: bottom
anchors.bottom: true 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 { component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border exclusiveZone: Config.barConfig.border
implicitHeight: 1 implicitHeight: 1
+18 -18
View File
@@ -78,7 +78,7 @@ CustomMouseArea {
const dragY = y - dragStart.y; const dragY = y - dragStart.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, 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; root.panels.drawing.expanded = false;
} }
@@ -96,25 +96,25 @@ CustomMouseArea {
if (dragY < -10) if (dragY < -10)
visibilities.dock = true; visibilities.dock = true;
if (panels.sidebar.width === 0) { if (panels.sidebar.width === 0) {
const showOsd = inRightPanel(panels.osdWrapper, x, y); const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) { if (showOsd) {
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
} }
} else {
const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
}
if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y)) if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
visibilities.dock = true; visibilities.dock = true;
+92 -6
View File
@@ -13,6 +13,7 @@ import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock import qs.Modules.Dock as Dock
import qs.Modules.SysTray.Popouts as SysPopouts
import qs.Config import qs.Config
Item { Item {
@@ -34,8 +35,10 @@ Item {
readonly property alias resourcesWrapper: resourcesWrapper readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property alias settings: settings readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias traySubmenus: traySubmenus
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -92,6 +95,79 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Item {
id: traySubmenus
Repeater {
model: popouts.content.state.submenus
CustomClippingRect {
id: subMenuWrapper
required property int index
required property var modelData
property real targetX: 0
property real targetY: 0
function updatePosition() {
let sourceItem = modelData.sourceItem;
if (!sourceItem || !sourceItem.parent)
return;
let mapped = sourceItem.mapToItem(root, 0, -Appearance.padding.small);
let rightX = mapped.x + modelData.sourceWidth + Config.barConfig.border;
let leftX = mapped.x - implicitWidth - Config.barConfig.border;
if (rightX + implicitWidth > root.width) {
targetX = leftX;
} else {
targetX = rightX;
}
targetY = mapped.y;
if (targetY + implicitHeight > root.height) {
targetY = root.height - implicitHeight;
}
if (targetY < 0)
targetY = 0;
}
implicitHeight: subMenuContent.implicitHeight + Appearance.padding.small * 2
implicitWidth: subMenuContent.implicitWidth + Appearance.padding.small * 2
radius: Appearance.rounding.normal
x: targetX
y: targetY
Behavior on implicitHeight {
Anim {
}
}
Behavior on implicitWidth {
Anim {
}
}
Component.onCompleted: {
updatePosition();
}
onImplicitHeightChanged: updatePosition()
onImplicitWidthChanged: updatePosition()
SysPopouts.SubMenu {
id: subMenuContent
anchors.centerIn: parent
handle: subMenuWrapper.modelData.handle
level: subMenuWrapper.index + 1
popouts: root.popouts.state
screen: root.screen
}
}
}
}
Modules.ClipWrapper { Modules.ClipWrapper {
id: popouts id: popouts
@@ -176,15 +252,25 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Settings.Wrapper { Item {
id: settings id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
// anchors.centerIn: parent clip: true
panels: root implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
screen: root.screen implicitWidth: settings.implicitWidth
visibilities: root.visibilities
Settings.Wrapper {
id: settings
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
// anchors.centerIn: parent
panels: root
screen: root.screen
visibilities: root.visibilities
}
} }
Dock.Wrapper { Dock.Wrapper {
+60 -19
View File
@@ -64,7 +64,7 @@ Variants {
height: win.height - bar.implicitHeight - Config.barConfig.border height: win.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor intersection: Intersection.Xor
regions: popoutRegions.instances regions: [...popoutRegions.instances, ...subMenuRegions.instances]
width: win.width - Config.barConfig.border * 2 width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border x: Config.barConfig.border
y: bar.implicitHeight y: bar.implicitHeight
@@ -93,6 +93,22 @@ Variants {
} }
} }
Variants {
id: subMenuRegions
model: panels.traySubmenus.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + panels.traySubmenus.x + Config.barConfig.border
y: modelData.y + panels.traySubmenus.y + bar.implicitHeight
}
}
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
@@ -158,6 +174,7 @@ Variants {
id: blobGroup id: blobGroup
color: DynamicColors.palette.m3surface color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color { Behavior on color {
CAnim { CAnim {
@@ -179,28 +196,34 @@ Variants {
PanelBg { PanelBg {
id: dashBg id: dashBg
deformAmount: 0.08 * Config.appearance.deform.scale property real extraHeight: 0.2
implicitHeight: panels.dashboard.height
deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width implicitWidth: panels.dashboard.width
panel: panels.dashboard panel: panels.dashboardWrapper
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border 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 { PanelBg {
id: launcherBg 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 panel: panels.launcher
radius: Appearance.rounding.smallest + 5 radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
} }
PanelBg { PanelBg {
id: sidebarBg id: sidebarBg
bottomLeftRadius: 0 bottomLeftRadius: 0
deformAmount: 0.08 * Config.appearance.deform.scale deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg] exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2 implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar panel: panels.sidebar
@@ -209,10 +232,10 @@ Variants {
PanelBg { PanelBg {
id: osdBg id: osdBg
deformAmount: 0.1 * Config.appearance.deform.scale deformAmount: 0.1
implicitHeight: panels.osd.height implicitHeight: panels.osd.height
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
panel: panels.osd panel: panels.osdWrapper
radius: 20 radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
@@ -227,7 +250,7 @@ Variants {
PanelBg { PanelBg {
id: utilsBg 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] exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities panel: panels.utilities
topLeftRadius: 0 topLeftRadius: 0
@@ -238,11 +261,11 @@ Variants {
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2 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) implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width implicitWidth: panels.popouts.width
panel: panels.popouts panel: panels.popoutsWrapper
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : Appearance.rounding.smallest 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 x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
@@ -255,10 +278,10 @@ Variants {
PanelBg { PanelBg {
id: resourcesBg id: resourcesBg
deformAmount: 0.08 * Config.appearance.deform.scale deformAmount: 0.05
implicitHeight: panels.resources.height implicitHeight: panels.resources.height
implicitWidth: panels.resources.width implicitWidth: panels.resources.width
panel: panels.resources panel: panels.resourcesWrapper
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
@@ -267,17 +290,23 @@ Variants {
PanelBg { PanelBg {
id: settingsBg 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 panel: panels.settings
radius: Appearance.rounding.large radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: 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 { PanelBg {
id: dockBg id: dockBg
deformAmount: 0.08 * Config.appearance.deform.scale deformAmount: 0.08
panel: panels.dock panel: panels.dock
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
} }
@@ -285,10 +314,22 @@ Variants {
PanelBg { PanelBg {
id: drawingBg id: drawingBg
deformAmount: 0.08 * Config.appearance.deform.scale deformAmount: 0.08
panel: panels.drawing panel: panels.drawing
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
} }
Repeater {
model: panels.traySubmenus.children
PanelBg {
required property Item modelData
deformAmount: 0.1
panel: modelData
radius: 20 * Appearance.rounding.scale
}
}
} }
Drawing { Drawing {
@@ -380,7 +421,7 @@ Variants {
property real deformAmount: 0.15 property real deformAmount: 0.15
required property Item panel required property Item panel
deformScale: deformAmount / 10000 deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
group: blobGroup group: blobGroup
implicitHeight: panel.height implicitHeight: panel.height
implicitWidth: panel.width implicitWidth: panel.width
-1
View File
@@ -346,7 +346,6 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
console.log("this is running");
if (root.gpuType === "GENERIC") { if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n"); const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
+20 -44
View File
@@ -7,23 +7,30 @@ import QtQuick
Singleton { Singleton {
id: root id: root
// The list of users that can log in graphically readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
// Each user object has: username, uid, home, shell, gecos (full name), face (avatar path) 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: [] property var users: []
// The currently selected user index function saveDefaultUser(): void {
property int selectedIndex: 0 if (selectedUser) {
defaultUserStorage.setText(selectedUser.username);
}
}
// The currently selected user object (or null if none) function selectNext(): void {
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null if (users.length === 0)
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
// Convenience property for the selected username function selectPrevious(): void {
readonly property string selectedUsername: selectedUser ? selectedUser.username : "" 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 { function selectUser(username: string): bool {
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
if (users[i].username === username) { if (users[i].username === username) {
@@ -34,28 +41,6 @@ Singleton {
return false; 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 { Process {
id: userLister id: userLister
@@ -67,13 +52,10 @@ Singleton {
try { try {
root.users = JSON.parse(text); root.users = JSON.parse(text);
// If we have users and no selection yet, try to select the default user
if (root.users.length > 0) { if (root.users.length > 0) {
// Try to load the default user
if (defaultUserStorage.loaded) { if (defaultUserStorage.loaded) {
const defaultUsername = defaultUserStorage.text().trim(); const defaultUsername = defaultUserStorage.text().trim();
if (defaultUsername && !root.selectUser(defaultUsername)) { if (defaultUsername && !root.selectUser(defaultUsername)) {
// Default user not found, select first user
root.selectedIndex = 0; root.selectedIndex = 0;
} }
} else { } else {
@@ -87,15 +69,14 @@ Singleton {
} }
} }
// FileView for persisting the default user
FileView { FileView {
id: defaultUserStorage id: defaultUserStorage
path: root.defaultUserFile path: root.defaultUserFile
preload: true preload: true
onLoadFailed: {}
onLoaded: { onLoaded: {
// If users are already loaded, try to select the default user
if (root.users.length > 0) { if (root.users.length > 0) {
const defaultUsername = text().trim(); const defaultUsername = text().trim();
if (defaultUsername) { 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");
}
} }
} }
+5 -5
View File
@@ -13,11 +13,11 @@ Singleton {
function setHyprConf(): void { function setHyprConf(): void {
Hypr.extras.applyOptions({ Hypr.extras.applyOptions({
"animations:enabled": 0, "animations.enabled": 0,
"decoration:shadow:enabled": 0, "decoration.shadow.enabled": 0,
"decoration:blur:enabled": 0, "decoration.blur.enabled": 0,
"general:border_size": 0, "general.border_size": 0,
"decoration:rounding": 0 "decoration.rounding": 0
}); });
} }
+1
View File
@@ -14,6 +14,7 @@ Searcher {
property string actualCurrent: WallpaperPath.currentWallpaperPath property string actualCurrent: WallpaperPath.currentWallpaperPath
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath property string previewPath
property bool recentlyChanged
property bool showPreview: false property bool showPreview: false
function preview(path: string): void { function preview(path: string): void {
+2
View File
@@ -16,6 +16,7 @@ Item {
readonly property Item current: currentPopout?.item ?? null readonly property Item current: currentPopout?.item ?? null
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
required property PopoutState popouts required property PopoutState popouts
required property ShellScreen screen
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2 implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2 implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
@@ -63,6 +64,7 @@ Item {
TrayMenuPopout { TrayMenuPopout {
popouts: root.popouts popouts: root.popouts
screen: root.screen
trayItem: trayMenu.modelData.menu trayItem: trayMenu.modelData.menu
} }
} }
-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"
}
}
+25 -77
View File
@@ -12,90 +12,29 @@ Item {
required property Canvas drawing required property Canvas drawing
property bool expanded: true property bool expanded: true
property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities required property var visibilities
anchors.leftMargin: (-implicitWidth - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: 0 implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth
visible: width > 0 opacity: 1 - offsetScale
visible: offsetScale < 1
states: [ Behavior on implicitWidth {
State { Anim {
name: "hidden" duration: Appearance.anim.durations.expressiveDefaultSpatial
when: !root.shouldBeActive easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
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: [ Behavior on offsetScale {
Transition { Anim {
from: "*" duration: Appearance.anim.durations.expressiveDefaultSpatial
to: "*" easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
ParallelAnimation {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
}
}
} }
] }
onVisibleChanged: { onVisibleChanged: {
if (!visible) if (!visible)
@@ -109,8 +48,12 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height height: content.contentItem.height
opacity: 1 opacity: root.expanded ? 0 : 1
Behavior on opacity {
Anim {
}
}
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: "arrow_forward_ios" text: "arrow_forward_ios"
@@ -122,7 +65,12 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
opacity: root.expanded ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: Content { sourceComponent: Content {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities visibilities: root.visibilities
-1
View File
@@ -13,7 +13,6 @@ Searcher {
function launch(entry: DesktopEntry): void { function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id); appDb.incrementFrequency(entry.id);
console.log(root.command);
if (entry.runInTerminal) if (entry.runInTerminal)
Quickshell.execDetached({ Quickshell.execDetached({
+2
View File
@@ -284,6 +284,8 @@ CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: { text: {
if (!Config.lock.showNotifContent)
return "Unlock to view";
const summary = modelData.summary.replace(/\n/g, " "); const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " "); const body = modelData.body.replace(/\n/g, " ");
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline; const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
+28
View File
@@ -1,8 +1,36 @@
import QtQuick import QtQuick
QtObject { QtObject {
id: root
property string currentName property string currentName
property bool hasCurrent property bool hasCurrent
property var submenus: []
signal detachRequested(mode: string) signal detachRequested(mode: string)
function clearSubmenus(): void {
submenus = [];
}
function closeSubmenus(level: int): void {
submenus = submenus.slice(0, level);
}
function pushSubmenu(level: int, handle: var, sourceItem: var, sourceWidth: int): void {
let newSubmenus = submenus.slice(0, level);
newSubmenus.push({
"handle": handle,
"sourceItem": sourceItem,
"sourceWidth": sourceWidth
});
submenus = newSubmenus;
}
onCurrentNameChanged: {
root.clearSubmenus();
}
onHasCurrentChanged: {
root.clearSubmenus();
}
} }
+1 -1
View File
@@ -17,7 +17,7 @@ CustomRect {
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
radius: height / 2 radius: Appearance.rounding.full
StateLayer { StateLayer {
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.visibilities.resources = !root.visibilities.resources
+7 -7
View File
@@ -29,13 +29,13 @@ SettingsPage {
step: 50 step: 50
} }
// Separator { Separator {
// } }
//
// WallpaperCropper { WallpaperCropper {
// Layout.fillWidth: true Layout.fillWidth: true
// Layout.preferredHeight: 300 Layout.preferredHeight: 600
// } }
} }
SettingsSection { SettingsSection {
+16 -6
View File
@@ -19,8 +19,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Height"
min: 1 min: 1
name: "Height"
object: Config.barConfig object: Config.barConfig
setting: "height" setting: "height"
} }
@@ -29,8 +29,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Rounding"
min: 0 min: 0
name: "Rounding"
object: Config.barConfig object: Config.barConfig
setting: "rounding" setting: "rounding"
} }
@@ -39,11 +39,21 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Border"
min: 0 min: 0
name: "Border"
object: Config.barConfig object: Config.barConfig
setting: "border" setting: "border"
} }
Separator {
}
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.barConfig
setting: "smoothing"
}
} }
SettingsSection { SettingsSection {
@@ -145,8 +155,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Dock height"
min: 1 min: 1
name: "Dock height"
object: Config.dock object: Config.dock
setting: "height" setting: "height"
} }
@@ -173,8 +183,8 @@ SettingsPage {
} }
SettingStringList { SettingStringList {
name: "Pinned apps"
addLabel: qsTr("Add pinned app") addLabel: qsTr("Add pinned app")
name: "Pinned apps"
object: Config.dock object: Config.dock
setting: "pinnedApps" setting: "pinnedApps"
} }
@@ -183,8 +193,8 @@ SettingsPage {
} }
SettingStringList { SettingStringList {
name: "Ignored app regexes"
addLabel: qsTr("Add ignored regex") addLabel: qsTr("Add ignored regex")
name: "Ignored app regexes"
object: Config.dock object: Config.dock
setting: "ignoredAppRegexes" setting: "ignoredAppRegexes"
} }
+14 -5
View File
@@ -31,8 +31,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Max fingerprint tries"
min: 1 min: 1
name: "Max fingerprint tries"
object: Config.lock object: Config.lock
setting: "maxFprintTries" setting: "maxFprintTries"
step: 1 step: 1
@@ -41,9 +41,18 @@ SettingsPage {
Separator { Separator {
} }
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSpinBox { SettingSpinBox {
name: "Blur amount"
min: 0 min: 0
name: "Blur amount"
object: Config.lock object: Config.lock
setting: "blurAmount" setting: "blurAmount"
step: 1 step: 1
@@ -53,9 +62,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Height multiplier"
max: 2 max: 2
min: 0.1 min: 0.1
name: "Height multiplier"
object: Config.lock.sizes object: Config.lock.sizes
setting: "heightMult" setting: "heightMult"
step: 0.05 step: 0.05
@@ -65,9 +74,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Aspect ratio"
max: 4 max: 4
min: 0.5 min: 0.5
name: "Aspect ratio"
object: Config.lock.sizes object: Config.lock.sizes
setting: "ratio" setting: "ratio"
step: 0.05 step: 0.05
@@ -77,8 +86,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Center width"
min: 100 min: 100
name: "Center width"
object: Config.lock.sizes object: Config.lock.sizes
setting: "centerWidth" setting: "centerWidth"
step: 10 step: 10
@@ -22,6 +22,13 @@ ColumnLayout {
Config.save(); 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) { function updateTimeoutEntry(i, key, value) {
const list = [...Config.general.idle.timeouts]; const list = [...Config.general.idle.timeouts];
let entry = list[i]; let entry = list[i];
@@ -49,6 +56,9 @@ ColumnLayout {
onAddActiveActionRequested: { onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", ""); root.updateTimeoutEntry(index, "activeAction", "");
} }
onDeleteRequested: function (index) {
root.deleteTimeoutEntry(index);
}
onFieldEdited: function (key, value) { onFieldEdited: function (key, value) {
root.updateTimeoutEntry(index, key, value); root.updateTimeoutEntry(index, key, value);
} }
+31 -8
View File
@@ -10,6 +10,7 @@ Item {
required property var modelData required property var modelData
signal addActiveActionRequested signal addActiveActionRequested
signal deleteRequested(int index)
signal fieldEdited(string key, var value) signal fieldEdited(string key, var value)
Layout.fillWidth: true Layout.fillWidth: true
@@ -65,42 +66,64 @@ Item {
HoverHandler { HoverHandler {
id: nameHover 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 { CustomText {
anchors.left: parent.left 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.rightMargin: Appearance.spacing.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight // enable if CustomText supports it elide: Text.ElideRight // enable if CustomText supports it
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: root.modelData.name text: root.modelData.name
visible: !nameCell.editing visible: !nameCell.editing
Behavior on anchors.leftMargin {
Anim {
}
}
} }
IconButton { HoverIconButton {
id: editButton id: deleteButton
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large font.pointSize: Appearance.font.size.large
icon: "edit" icon: "delete"
visible: nameHover.hovered && !nameCell.editing shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit() onClicked: root.deleteRequested(root.index)
} }
CustomRect { CustomRect {
anchors.fill: parent anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surface 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 radius: Appearance.rounding.small
visible: nameCell.editing visible: nameCell.editing
CustomTextField { CustomTextField {
id: nameEditor 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 text: nameCell.draftName
Keys.onEscapePressed: { Keys.onEscapePressed: {
@@ -38,7 +38,7 @@ Item {
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5 Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500 Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: 21 + Appearance.padding.normal radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
CustomRect { CustomRect {
id: searchBox id: searchBox
+89 -77
View File
@@ -6,97 +6,109 @@ import qs.Config
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
ColumnLayout { Item {
id: root id: root
spacing: 15 Image {
width: Math.min(parent ? parent.width : 600, 600) id: imageView
Rectangle { property real displayH: paintedHeight
id: previewContainer property real displayW: paintedWidth
property real displayX: (width - paintedWidth) * 0.5
property real displayY: (height - paintedHeight) * 0.5
property real scaleX: sourceW / displayW
property real scaleY: sourceH / displayH
property real sourceH: Quickshell.screens[0].height
property real sourceW: Quickshell.screens[0].width
anchors.fill: parent
fillMode: Image.PreserveAspectFit
smooth: true
source: Wallpapers.current
}
Item {
id: overlay
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 clip: true
color: DynamicColors.palette.m3surfaceContainer height: imageView.displayH
radius: Config.appearance.rounding.scale * 10 width: imageView.displayW
x: imageView.displayX
y: imageView.displayY
Image { CustomRect {
id: img id: cropRect
property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height
readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY)
property real zoom: Config.background.zoom
function clampToBounds() {
x = Math.max(0, Math.min(x, overlay.width - width));
y = Math.max(0, Math.min(y, overlay.height - height));
}
border.color: DynamicColors.palette.m3primary
border.width: 2
color: DynamicColors.tPalette.m3primary
height: width / aspectRatio
radius: Appearance.rounding.small
visible: imageView.status === Image.Ready
width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom)
x: Config.background.sourceClipX / imageView.scaleX
y: Config.background.sourceClipY / imageView.scaleY
}
MouseArea {
function updateCrop(mouseX, mouseY) {
let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5;
nx = Math.max(0, Math.min(nx, overlay.width - cropRect.width));
ny = Math.max(0, Math.min(ny, overlay.height - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent anchors.fill: parent
asynchronous: true hoverEnabled: true
fillMode: Image.PreserveAspectFit preventStealing: true
source: Wallpapers.current
Rectangle { onPositionChanged: mouse => {
id: cropRect if (pressed)
updateCrop(mouse.x, mouse.y);
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
}
onReleased: {
Wallpapers.recentlyChanged = false;
Config.background.sourceClipX = cropRect.sourceRect.x;
Config.background.sourceClipY = cropRect.sourceRect.y;
Config.background.sourceClipW = cropRect.sourceRect.width;
Config.background.sourceClipH = cropRect.sourceRect.height;
Config.save();
}
onWheel: wheel => {
let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5;
property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom if (wheel.angleDelta.y > 0)
property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom cropRect.zoom *= 1.1;
property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight) else
property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height cropRect.zoom /= 1.1;
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
border.color: DynamicColors.palette.m3primary cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0));
border.width: 2 Config.background.zoom = cropRect.zoom;
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
DragHandler { cropRect.x = oldCenterX - cropRect.width * 0.5;
target: null cropRect.y = oldCenterY - cropRect.height * 0.5;
onActiveTranslationChanged: { cropRect.clampToBounds();
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();
}
}
}
}
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));
}
}
}
} }
} }
} }
SettingSpinBox {
max: 5.0
min: 1.0
name: "Zoom"
object: Config.background
setting: "zoom"
step: 0.1
}
} }
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.Components
import qs.Config import qs.Config
import "../../scripts/fuzzysort.js" as Fuzzy import "../../scripts/fuzzysort.js" as Fuzzy
import "./SettingsIndex.mjs" as SettingsIndex import "../../scripts/SettingsIndex.mjs" as SettingsIndex
Item { Item {
id: root id: root
@@ -53,11 +53,10 @@ Item {
Shortcut { Shortcut {
sequence: "/" sequence: "/"
onActivated: searchField.forceActiveFocus() onActivated: searchField.forceActiveFocus()
} }
Component.onCompleted: console.log(root.height)
ListModel { ListModel {
id: resultsModel id: resultsModel
} }
+10 -31
View File
@@ -7,45 +7,24 @@ import qs.Helpers
Item { Item {
id: root id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: 0 anchors.topMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
visible: height > 0 opacity: 1 - offsetScale
visible: offsetScale < 1
states: State { Behavior on offsetScale {
name: "visible" Anim {
when: root.visibilities.settings duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
PropertyChanges {
root.implicitHeight: content.implicitHeight
} }
} }
transitions: [
Transition {
from: ""
to: "visible"
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
}
}
]
CustomClippingRect { CustomClippingRect {
anchors.fill: parent anchors.fill: parent
-1
View File
@@ -19,7 +19,6 @@ Scope {
if (!root.launcherInterrupted && !root.hasFullscreen) { if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive(); const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher; visibilities.launcher = !visibilities.launcher;
console.log(root.launcherInterrupted);
} }
root.launcherInterrupted = false; root.launcherInterrupted = false;
} }
+158
View File
@@ -0,0 +1,158 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Components
import qs.Modules
import qs.Config
Column {
id: menu
property int biggestWidth: 0
required property QsMenuHandle handle
required property int level
required property PopoutState popouts
required property ShellScreen screen
property bool shown: true
height: childrenRect.height
opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8
spacing: 4
width: biggestWidth
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
QsMenuOpener {
id: menuOpener
menu: menu.handle
}
Repeater {
model: menuOpener.children
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: menu.biggestWidth
radius: Appearance.rounding.full
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
Loader {
id: children
active: !item.modelData.isSeparator
anchors.left: parent.left
anchors.right: parent.right
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
StateLayer {
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
menu.popouts.pushSubmenu(menu.level, entry, item, menu.biggestWidth);
} else {
entry.triggered();
menu.popouts.hasCurrent = false;
}
}
disabled: !item.modelData.enabled
radius: item.radius
}
Loader {
id: icon
active: item.modelData.icon !== ""
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
IconImage {
id: iconImage
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
MultiEffect {
anchors.fill: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
source: iconImage
}
}
}
CustomText {
id: label
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
TextMetrics {
id: labelMetrics
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text
Component.onCompleted: {
var biggestWidth = menu.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
menu.biggestWidth = currentWidth;
}
}
}
Loader {
id: expand
active: item.modelData.hasChildren
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: "chevron_right"
}
}
}
}
}
}
}
+4 -237
View File
@@ -1,249 +1,16 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Components import qs.Components
import qs.Modules import qs.Modules
import qs.Config import qs.Config
StackView { SubMenu {
id: root id: root
property int biggestWidth: 0 handle: trayItem
required property PopoutState popouts level: 0
property int rootWidth: 0
required property QsMenuHandle trayItem required property QsMenuHandle trayItem
implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth
initialItem: SubMenu {
handle: root.trayItem
}
popEnter: NoAnim {
}
popExit: NoAnim {
}
pushEnter: NoAnim {
}
pushExit: NoAnim {
}
Component {
id: subMenuComp
SubMenu {
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: Column {
id: menu
required property QsMenuHandle handle
property bool isSubMenu
property bool shown
opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8
spacing: 4
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
QsMenuOpener {
id: menuOpener
menu: menu.handle
}
Repeater {
model: menuOpener.children
CustomRect {
id: item
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
Loader {
id: children
active: !item.modelData.isSeparator
anchors.left: parent.left
anchors.right: parent.right
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
StateLayer {
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
root.rootWidth = root.biggestWidth;
root.biggestWidth = 0;
root.push(subMenuComp.createObject(null, {
handle: entry,
isSubMenu: true
}));
} else {
item.modelData.triggered();
root.popouts.hasCurrent = false;
}
}
disabled: !item.modelData.enabled
radius: item.radius
}
Loader {
id: icon
active: item.modelData.icon !== ""
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
IconImage {
id: iconImage
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
MultiEffect {
anchors.fill: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
source: iconImage
}
}
}
CustomText {
id: label
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
TextMetrics {
id: labelMetrics
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
}
Loader {
id: expand
active: item.modelData.hasChildren
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: "chevron_right"
}
}
}
}
}
}
Loader {
id: loader
active: menu.isSubMenu
asynchronous: true
sourceComponent: Item {
implicitHeight: back.implicitHeight + 2 / 2
implicitWidth: back.implicitWidth
Item {
anchors.bottom: parent.bottom
implicitHeight: back.implicitHeight
implicitWidth: back.implicitWidth + 10
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.smallest / 2
StateLayer {
function onClicked(): void {
root.pop();
root.biggestWidth = root.rootWidth;
}
color: DynamicColors.palette.m3onSecondaryContainer
radius: parent.radius
}
}
Row {
id: back
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: "chevron_left"
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: qsTr("Back")
}
}
}
}
}
}
} }
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent" // color: "transparent"
radius: 6 radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
CustomRect { CustomRect {
id: indicator id: indicator
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2 implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
radius: height / 2 radius: Appearance.rounding.full
RowLayout { RowLayout {
id: contentRow 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;
}
}
}
}
+19 -63
View File
@@ -1,5 +1,6 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
@@ -8,79 +9,34 @@ import qs.Config
Item { Item {
id: root id: root
property Image current: one required property ShellScreen screen
property string source: Wallpapers.current property string source: Wallpapers.current
anchors.fill: parent anchors.fill: parent
Component.onCompleted: { Image {
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 {
id: img id: img
property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height) anchors.fill: parent
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)
function update(): void {
if (path === root.source) {
root.current = this;
} else {
path = root.source;
}
}
anchors.fill: undefined
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height opacity: 1
opacity: 0 retainWhileLoading: true
scale: Wallpapers.showPreview ? 1 : 0.8 source: root.source
width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width sourceClipRect: Wallpapers.recentlyChanged ? null : Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
x: isValid ? (root.width - width) * Config.background.alignX : 0 sourceSize.height: root.screen.height
y: isValid ? (root.height - height) * Config.background.alignY : 0 sourceSize.width: root.screen.width
states: State { onSourceChanged: {
name: "visible" if (Wallpapers.recentlyChanged) {
when: root.current === img Config.background.sourceClipH = 0;
Config.background.sourceClipW = 0;
PropertyChanges { Config.background.sourceClipY = 0;
img.opacity: 1 Config.background.sourceClipX = 0;
img.scale: 1 Config.background.zoom = 1.0;
} Config.save();
}
transitions: Transition {
Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
}
}
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
} }
Wallpapers.recentlyChanged = true;
} }
} }
} }
+1
View File
@@ -30,6 +30,7 @@ Loader {
} }
WallBackground { WallBackground {
screen: root.screen
} }
Loader { Loader {
+2 -1
View File
@@ -79,7 +79,8 @@ Item {
} }
onPressed: { onPressed: {
Hyprland.dispatch(`workspace ${button.modelData.name}`); const ws = button.modelData.name;
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
} }
} }
} }
+3 -1
View File
@@ -15,13 +15,14 @@ Item {
property real currentCenter property real currentCenter
property alias currentName: popoutState.currentName property alias currentName: popoutState.currentName
property string detachedMode property string detachedMode
readonly property bool isDetached: detachedMode.length > 0
property alias hasCurrent: popoutState.hasCurrent property alias hasCurrent: popoutState.hasCurrent
readonly property bool isDetached: detachedMode.length > 0
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
required property real offsetScale required property real offsetScale
property string queuedMode property string queuedMode
required property ShellScreen screen required property ShellScreen screen
property alias state: popoutState
function close(): void { function close(): void {
hasCurrent = false; hasCurrent = false;
@@ -79,6 +80,7 @@ Item {
sourceComponent: Content { sourceComponent: Content {
popouts: popoutState popouts: popoutState
screen: root.screen
} }
} }
+1
View File
@@ -12,6 +12,7 @@ qml_module(ZShell-blobs
qt_add_shaders(ZShell-blobs "blob_shaders" qt_add_shaders(ZShell-blobs "blob_shaders"
BATCHABLE OPTIMIZED NOHLSL NOMSL BATCHABLE OPTIMIZED NOHLSL NOMSL
GLSL "300es,330"
PREFIX "/" PREFIX "/"
FILES FILES
shaders/blob.frag shaders/blob.frag
+7 -1
View File
@@ -2,6 +2,9 @@
#include <cstring> #include <cstring>
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
"BlobMaterial packs excludeMask into a float slot via memcpy");
QSGMaterialType* BlobMaterial::type() const { QSGMaterialType* BlobMaterial::type() const {
static QSGMaterialType s_type; static QSGMaterialType s_type;
return &s_type; return &s_type;
@@ -82,8 +85,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
const auto& r = mat->m_rects[i]; const auto& r = mat->m_rects[i];
const int base = 160 + i * 80; 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 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 }; const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
memcpy(buf->data() + base, d0, 16); memcpy(buf->data() + base, d0, 16);
memcpy(buf->data() + base + 16, d1, 16); memcpy(buf->data() + base + 16, d1, 16);
+3
View File
@@ -14,6 +14,9 @@ struct BlobRectData {
float screenHalfX = 0, screenHalfY = 0; float screenHalfX = 0, screenHalfY = 0;
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU // Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
float radius[4] = { 0, 0, 0, 0 }; 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 { 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 // Accumulate sub-pixel drift so slow movements don't desync the shader
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x()); m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y()); m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
const auto dw = std::abs(newGeometry.width() - oldGeometry.width()); // Accumulate size delta across multiple frames so incremental size
const auto dh = std::abs(newGeometry.height() - oldGeometry.height()); // changes that are each below the threshold still trigger a dirty
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { // 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_pendingDx = 0;
m_pendingDy = 0; m_pendingDy = 0;
m_pendingDw = 0;
m_pendingDh = 0;
m_group->markShapeDirty(this); m_group->markShapeDirty(this);
} }
} }
@@ -149,6 +155,10 @@ void BlobShape::updatePolish() {
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY), const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH)); 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()) { for (BlobShape* other : m_group->shapes()) {
if (other->isInvertedRect()) if (other->isInvertedRect())
continue; continue;
@@ -210,12 +220,29 @@ void BlobShape::updatePolish() {
r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh; r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh;
m_cachedRects.append(r); m_cachedRects.append(r);
rectShapes.append(other);
} }
} }
if (isInvertedRect()) if (isInvertedRect())
m_cachedMyIndex = -1; 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 // Cache inverted rect data
m_cachedHasInverted = false; m_cachedHasInverted = false;
m_cachedInvertedRadius = 0; m_cachedInvertedRadius = 0;
@@ -270,6 +297,7 @@ void BlobShape::updatePolish() {
const auto rectCount = m_cachedRects.size(); const auto rectCount = m_cachedRects.size();
for (qsizetype i = 0; i < rectCount; ++i) { for (qsizetype i = 0; i < rectCount; ++i) {
auto& ri = m_cachedRects[i]; auto& ri = m_cachedRects[i];
const int riExcludeMask = ri.excludeMask;
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f; 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; 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) { for (qsizetype j = 0; j < rectCount; ++j) {
if (j == i) if (j == i)
continue; continue;
if (riExcludeMask & (1 << j))
continue;
const auto& rj = m_cachedRects[j]; 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))); 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))); 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; QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2; int m_cachedMyIndex = -2;
float m_pendingDx = 0; float m_pendingDx = 0;
float m_pendingDy = 0; float m_pendingDy = 0;
bool m_cachedHasInverted = false; float m_pendingDw = 0;
float m_pendingDh = 0;
bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
+36 -5
View File
@@ -63,13 +63,17 @@ float smaxSharpA(float a, float b, float k) {
void main() { void main() {
vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH); 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; int owner = -2;
float minDist = 1e10; float minDist = 1e10;
for (int i = 0; i < rectCount; i++) { for (int i = 0; i < rectCount; i++) {
vec4 rect = rectData[i * 5]; // cx, cy, hw, hh 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 invDm = rectData[i * 5 + 2]; // inverse deform matrix
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0 vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0
vec4 radii = vec4 radii =
@@ -81,8 +85,10 @@ void main() {
// AABB early-out: skip rects far from this pixel // AABB early-out: skip rects far from this pixel
vec2 extent = sh.xy + vec2(smoothFactor * 1.5); vec2 extent = sh.xy + vec2(smoothFactor * 1.5);
if (abs(pixel.x - center.x) > extent.x || 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; continue;
}
// Apply pre-computed inverse deformation to the evaluation point // Apply pre-computed inverse deformation to the evaluation point
mat2 invDeform = mat2(invDm.xy, invDm.zw); mat2 invDeform = mat2(invDm.xy, invDm.zw);
@@ -138,13 +144,38 @@ void main() {
d *= scale; d *= scale;
} }
mergedSdf = smin(mergedSdf, d, smoothFactor); dArr[i] = d;
if (d < smoothFactor && d < minDist) { if (d < smoothFactor && d < minDist) {
minDist = d; minDist = d;
owner = i; 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) { if (hasInverted != 0) {
float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0; float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
float dInner = float dInner =
+309 -142
View File
@@ -1,217 +1,384 @@
#include "hyprextras.hpp" #include "hyprextras.hpp"
#include "hyprdevices.hpp"
#include <qdir.h> #include <qdir.h>
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qloggingcategory.h>
#include <qmetatype.h>
#include <qregularexpression.h>
#include <qvariant.h> #include <qvariant.h>
Q_LOGGING_CATEGORY(lcHypr, "ZShell.internal.hypr", QtInfoMsg)
namespace ZShell::internal::hypr { 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) HyprExtras::HyprExtras(QObject* parent)
: QObject(parent) : QObject(parent)
, m_requestSocket("") , m_requestSocket("")
, m_eventSocket("") , m_eventSocket("")
, m_socket(nullptr) , m_socket(nullptr)
, m_socketValid(false) , m_socketValid(false)
, m_devices(new HyprDevices(this)) { , m_devices(new HyprDevices(this)) {
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE"); const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
if (his.isEmpty()) { if (his.isEmpty()) {
qWarning() qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
<< "HyprExtras::HyprExtras: $HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket."; return;
return; }
}
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his); auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
hyprDir = "/tmp/hypr/" + his; hyprDir = QStringLiteral("/tmp/hypr/") + his;
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
qWarning() << "HyprExtras::HyprExtras: Hyprland socket directory does not exist. Unable to connect to " qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket.";
"Hyprland socket."; return;
return; }
} }
}
m_requestSocket = hyprDir + "/.socket.sock"; m_requestSocket = hyprDir + QStringLiteral("/.socket.sock");
m_eventSocket = hyprDir + "/.socket2.sock"; m_eventSocket = hyprDir + QStringLiteral("/.socket2.sock");
refreshOptions(); refreshOptions();
refreshDevices(); refreshDevices();
m_socket = new QLocalSocket(this); m_socket = new QLocalSocket(this);
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError); QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged); QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged);
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent); QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
} }
QVariantHash HyprExtras::options() const { QVariantHash HyprExtras::options() const {
return m_options; return m_options;
} }
HyprDevices* HyprExtras::devices() const { HyprDevices* HyprExtras::devices() const {
return m_devices; return m_devices;
} }
void HyprExtras::message(const QString& message) { void HyprExtras::message(const QString& message) {
if (message.isEmpty()) { if (message.isEmpty()) {
return; return;
} }
makeRequest(message, [](bool success, const QByteArray& res) { makeRequest(message, [](bool success, const QByteArray& res) {
if (!success) { if (!success) {
qWarning() << "HyprExtras::message: request error:" << QString::fromUtf8(res); qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res);
} }
}); });
} }
void HyprExtras::batchMessage(const QStringList& messages) { void HyprExtras::batchMessage(const QStringList& messages) {
if (messages.isEmpty()) { if (messages.isEmpty()) {
return; return;
} }
makeRequest("[[BATCH]]" + messages.join(";"), [](bool success, const QByteArray& res) { makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
if (!success) { [](bool success, const QByteArray& res) {
qWarning() << "HyprExtras::batchMessage: request error:" << QString::fromUtf8(res); if (!success) {
} qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res);
}); }
});
} }
void HyprExtras::applyOptions(const QVariantHash& options) { void HyprExtras::applyOptions(const QVariantHash& options) {
if (options.isEmpty()) { if (options.isEmpty()) {
return; return;
} }
QString request = "[[BATCH]]"; QStringList calls;
for (auto it = options.constBegin(); it != options.constEnd(); ++it) { calls.reserve(options.size());
request += QString("keyword %1 %2;").arg(it.key(), it.value().toString());
}
makeRequest(request, [this](bool success, const QByteArray& res) { for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
if (success) { const auto call = buildHlConfigCall(it.key(), it.value());
refreshOptions(); if (!call.isEmpty()) {
} else { calls << call;
qWarning() << "HyprExtras::applyOptions: request error" << QString::fromUtf8(res); }
} }
});
if (calls.isEmpty()) {
return;
}
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
if (success) {
refreshOptions();
} else {
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res);
}
});
} }
void HyprExtras::refreshOptions() { void HyprExtras::refreshOptions() {
if (!m_optionsRefresh.isNull()) { if (!m_optionsRefresh.isNull()) {
m_optionsRefresh->close(); 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(); m_optionsRefresh.reset();
if (!success) { if (!success) {
return; return;
} }
const auto options = response.array(); const auto options = response.array();
bool dirty = false; bool dirty = false;
for (const auto& o : std::as_const(options)) { for (const auto& o : std::as_const(options)) {
const auto obj = o.toObject(); const auto obj = o.toObject();
const auto key = obj.value("value").toString(); const auto key = obj.value(QStringLiteral("value")).toString();
const auto value = obj.value("data").toObject().value("current").toVariant(); 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);
}
}
if (dirty) { if (m_options.value(key) != value) {
emit optionsChanged(); dirty = true;
} m_options.insert(key, value);
}); }
}
if (dirty) {
emit optionsChanged();
}
});
} }
void HyprExtras::refreshDevices() { void HyprExtras::refreshDevices() {
if (!m_devicesRefresh.isNull()) { if (!m_devicesRefresh.isNull()) {
m_devicesRefresh->close(); 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(); m_devicesRefresh.reset();
if (success) { if (success) {
m_devices->updateLastIpcObject(response.object()); m_devices->updateLastIpcObject(response.object());
} }
}); });
} }
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const { void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
if (!m_socketValid) { 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 { } else {
qWarning() << "HyprExtras::socketError: Hyprland event socket error:" << error; qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error;
} }
} }
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) { void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
if (state == QLocalSocket::UnconnectedState && m_socketValid) { 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; m_socketValid = state == QLocalSocket::ConnectedState;
} }
void HyprExtras::readEvent() { void HyprExtras::readEvent() {
while (true) { while (true) {
auto rawEvent = m_socket->readLine(); auto rawEvent = m_socket->readLine();
if (rawEvent.isEmpty()) { if (rawEvent.isEmpty()) {
break; break;
} }
rawEvent.truncate(rawEvent.length() - 1); // Remove trailing \n rawEvent.truncate(rawEvent.length() - 1);
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>")); const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
handleEvent(QString::fromUtf8(event)); handleEvent(QString::fromUtf8(event));
} }
} }
void HyprExtras::handleEvent(const QString& event) { void HyprExtras::handleEvent(const QString& event) {
if (event == "configreloaded") { if (event == QStringLiteral("configreloaded")) {
refreshOptions(); refreshOptions();
} else if (event == "activelayout") { } else if (event == QStringLiteral("activelayout")) {
refreshDevices(); refreshDevices();
} }
} }
HyprExtras::SocketPtr HyprExtras::makeRequestJson( HyprExtras::SocketPtr HyprExtras::makeRequestJson(
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) { 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)); callback(success, QJsonDocument::fromJson(response));
}); });
} }
HyprExtras::SocketPtr HyprExtras::makeRequest( HyprExtras::SocketPtr HyprExtras::makeRequest(
const QString& request, const std::function<void(bool, QByteArray)>& callback) { const QString& request, const std::function<void(bool, QByteArray)>& callback) {
if (m_requestSocket.isEmpty()) { if (m_requestSocket.isEmpty()) {
return SocketPtr(); return SocketPtr();
} }
auto socket = SocketPtr::create(this); auto socket = SocketPtr::create(this);
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() { QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() { QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
const auto response = socket->readAll(); const auto response = socket->readAll();
callback(true, std::move(response)); callback(true, std::move(response));
socket->close(); socket->close();
}); });
socket->write(request.toUtf8()); socket->write(request.toUtf8());
socket->flush(); socket->flush();
}); });
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) { 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, {}); callback(false, {});
socket->close(); socket->close();
}); });
socket->connectToServer(m_requestSocket); socket->connectToServer(m_requestSocket);
return socket; return socket;
} }
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+34 -30
View File
@@ -1,56 +1,60 @@
#pragma once #pragma once
#include "hyprdevices.hpp"
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qsharedpointer.h>
#include <qvariant.h>
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
class HyprExtras : public QObject { class HyprDevices;
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged) class HyprExtras : public QObject {
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT) 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: public:
explicit HyprExtras(QObject* parent = nullptr); explicit HyprExtras(QObject* parent = nullptr);
[[nodiscard]] QVariantHash options() const; [[nodiscard]] QVariantHash options() const;
[[nodiscard]] HyprDevices* devices() const; [[nodiscard]] HyprDevices* devices() const;
Q_INVOKABLE void message(const QString& message); Q_INVOKABLE void message(const QString& message);
Q_INVOKABLE void batchMessage(const QStringList& messages); Q_INVOKABLE void batchMessage(const QStringList& messages);
Q_INVOKABLE void applyOptions(const QVariantHash& options); Q_INVOKABLE void applyOptions(const QVariantHash& options);
Q_INVOKABLE void refreshOptions(); Q_INVOKABLE void refreshOptions();
Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void refreshDevices();
signals: signals:
void optionsChanged(); void optionsChanged();
private: private:
using SocketPtr = QSharedPointer<QLocalSocket>; using SocketPtr = QSharedPointer<QLocalSocket>;
QString m_requestSocket; QString m_requestSocket;
QString m_eventSocket; QString m_eventSocket;
QLocalSocket* m_socket; QLocalSocket* m_socket;
bool m_socketValid; bool m_socketValid;
QVariantHash m_options; QVariantHash m_options;
HyprDevices* const m_devices; HyprDevices* const m_devices;
SocketPtr m_optionsRefresh; SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh; SocketPtr m_devicesRefresh;
void socketError(QLocalSocket::LocalSocketError error) const; void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state); void socketStateChanged(QLocalSocket::LocalSocketState state);
void readEvent(); void readEvent();
void handleEvent(const QString& event); void handleEvent(const QString& event);
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& 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); SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
}; };
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
@@ -9,14 +9,8 @@ _data = {
"variants": { "variants": {
"type": "multi", "type": "multi",
"defaults": { "defaults": {
"dark": { "dark": {"m3flavor": "mocha", "m3accent": "mauve"},
"m3flavor": "mocha", "light": {"m3flavor": "latte", "m3accent": "mauve"},
"m3accent": "mauve"
},
"light": {
"m3flavor": "latte",
"m3accent": "mauve"
}
}, },
"flavors": [ "flavors": [
{ {
@@ -35,8 +29,8 @@ _data = {
"m3surfaceContainerHighest": "#dce0e8", "m3surfaceContainerHighest": "#dce0e8",
"m3error": "#d20f39", "m3error": "#d20f39",
"m3warning": "#fe640b", "m3warning": "#fe640b",
"m3info": "#1e66f5" "m3info": "#1e66f5",
} },
}, },
{ {
"id": "frappe", "id": "frappe",
@@ -54,8 +48,8 @@ _data = {
"m3surfaceContainerHighest": "#232634", "m3surfaceContainerHighest": "#232634",
"m3error": "#e78284", "m3error": "#e78284",
"m3warning": "#ef9f76", "m3warning": "#ef9f76",
"m3info": "#8caaee" "m3info": "#8caaee",
} },
}, },
{ {
"id": "macchiato", "id": "macchiato",
@@ -73,8 +67,8 @@ _data = {
"m3surfaceContainerHighest": "#181926", "m3surfaceContainerHighest": "#181926",
"m3error": "#ed8796", "m3error": "#ed8796",
"m3warning": "#f5a97f", "m3warning": "#f5a97f",
"m3info": "#8aadf4" "m3info": "#8aadf4",
} },
}, },
{ {
"id": "mocha", "id": "mocha",
@@ -92,9 +86,9 @@ _data = {
"m3surfaceContainerHighest": "#11111b", "m3surfaceContainerHighest": "#11111b",
"m3error": "#f38ba8", "m3error": "#f38ba8",
"m3warning": "#fab387", "m3warning": "#fab387",
"m3info": "#89b4fa" "m3info": "#89b4fa",
} },
} },
], ],
"accents": [ "accents": [
{ {
@@ -105,29 +99,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#e1a99d", "m3primaryContainer": "#e1a99d",
"m3secondary": "#d8c7c4", "m3secondary": "#d8c7c4",
"m3surfaceTint": "#e1a99d" "m3surfaceTint": "#e1a99d",
}, },
"frappe": { "frappe": {
"m3primary": "#f2d5cf", "m3primary": "#f2d5cf",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#b8a5a6", "m3primaryContainer": "#b8a5a6",
"m3secondary": "#a2748b", "m3secondary": "#a2748b",
"m3surfaceTint": "#b8a5a6" "m3surfaceTint": "#b8a5a6",
}, },
"macchiato": { "macchiato": {
"m3primary": "#f4dbd6", "m3primary": "#f4dbd6",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b6a6a7", "m3primaryContainer": "#b6a6a7",
"m3secondary": "#9f6f8d", "m3secondary": "#9f6f8d",
"m3surfaceTint": "#b6a6a7" "m3surfaceTint": "#b6a6a7",
}, },
"mocha": { "mocha": {
"m3primary": "#f5e0dc", "m3primary": "#f5e0dc",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b5a6a8", "m3primaryContainer": "#b5a6a8",
"m3secondary": "#9d6d87", "m3secondary": "#9d6d87",
"m3surfaceTint": "#b5a6a8" "m3surfaceTint": "#b5a6a8",
} },
}, },
{ {
"id": "flamingo", "id": "flamingo",
@@ -137,29 +131,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#e29c9d", "m3primaryContainer": "#e29c9d",
"m3secondary": "#d7c3c4", "m3secondary": "#d7c3c4",
"m3surfaceTint": "#e29c9d" "m3surfaceTint": "#e29c9d",
}, },
"frappe": { "frappe": {
"m3primary": "#eebebe", "m3primary": "#eebebe",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#b5949a", "m3primaryContainer": "#b5949a",
"m3secondary": "#9d6b80", "m3secondary": "#9d6b80",
"m3surfaceTint": "#b5949a" "m3surfaceTint": "#b5949a",
}, },
"macchiato": { "macchiato": {
"m3primary": "#f0c6c6", "m3primary": "#f0c6c6",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b3979c", "m3primaryContainer": "#b3979c",
"m3secondary": "#996780", "m3secondary": "#996780",
"m3surfaceTint": "#b3979c" "m3surfaceTint": "#b3979c",
}, },
"mocha": { "mocha": {
"m3primary": "#f2cdcd", "m3primary": "#f2cdcd",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b3999e", "m3primaryContainer": "#b3999e",
"m3secondary": "#98667c", "m3secondary": "#98667c",
"m3surfaceTint": "#b3999e" "m3surfaceTint": "#b3999e",
} },
}, },
{ {
"id": "pink", "id": "pink",
@@ -169,29 +163,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#eb9bd7", "m3primaryContainer": "#eb9bd7",
"m3secondary": "#d9c7d5", "m3secondary": "#d9c7d5",
"m3surfaceTint": "#eb9bd7" "m3surfaceTint": "#eb9bd7",
}, },
"frappe": { "frappe": {
"m3primary": "#f4b8e4", "m3primary": "#f4b8e4",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#b990b5", "m3primaryContainer": "#b990b5",
"m3secondary": "#996e9e", "m3secondary": "#996e9e",
"m3surfaceTint": "#b990b5" "m3surfaceTint": "#b990b5",
}, },
"macchiato": { "macchiato": {
"m3primary": "#f5bde6", "m3primary": "#f5bde6",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b791b2", "m3primaryContainer": "#b791b2",
"m3secondary": "#95689a", "m3secondary": "#95689a",
"m3surfaceTint": "#b791b2" "m3surfaceTint": "#b791b2",
}, },
"mocha": { "mocha": {
"m3primary": "#f5c2e7", "m3primary": "#f5c2e7",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b591b0", "m3primaryContainer": "#b591b0",
"m3secondary": "#966597", "m3secondary": "#966597",
"m3surfaceTint": "#b591b0" "m3surfaceTint": "#b591b0",
} },
}, },
{ {
"id": "mauve", "id": "mauve",
@@ -201,29 +195,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#a670f1", "m3primaryContainer": "#a670f1",
"m3secondary": "#c2b8d0", "m3secondary": "#c2b8d0",
"m3surfaceTint": "#a670f1" "m3surfaceTint": "#a670f1",
}, },
"frappe": { "frappe": {
"m3primary": "#ca9ee6", "m3primary": "#ca9ee6",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#9c7eb6", "m3primaryContainer": "#9c7eb6",
"m3secondary": "#7d6799", "m3secondary": "#7d6799",
"m3surfaceTint": "#9c7eb6" "m3surfaceTint": "#9c7eb6",
}, },
"macchiato": { "macchiato": {
"m3primary": "#c6a0f6", "m3primary": "#c6a0f6",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#967cbe", "m3primaryContainer": "#967cbe",
"m3secondary": "#766597", "m3secondary": "#766597",
"m3surfaceTint": "#967cbe" "m3surfaceTint": "#967cbe",
}, },
"mocha": { "mocha": {
"m3primary": "#cba6f7", "m3primary": "#cba6f7",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#977ebb", "m3primaryContainer": "#977ebb",
"m3secondary": "#756294", "m3secondary": "#756294",
"m3surfaceTint": "#977ebb" "m3surfaceTint": "#977ebb",
} },
}, },
{ {
"id": "red", "id": "red",
@@ -233,29 +227,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#da5371", "m3primaryContainer": "#da5371",
"m3secondary": "#c0a0a8", "m3secondary": "#c0a0a8",
"m3surfaceTint": "#da5371" "m3surfaceTint": "#da5371",
}, },
"frappe": { "frappe": {
"m3primary": "#e78284", "m3primary": "#e78284",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#b06a72", "m3primaryContainer": "#b06a72",
"m3secondary": "#8b5d66", "m3secondary": "#8b5d66",
"m3surfaceTint": "#b06a72" "m3surfaceTint": "#b06a72",
}, },
"macchiato": { "macchiato": {
"m3primary": "#ed8796", "m3primary": "#ed8796",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b16b7a", "m3primaryContainer": "#b16b7a",
"m3secondary": "#865a69", "m3secondary": "#865a69",
"m3surfaceTint": "#b16b7a" "m3surfaceTint": "#b16b7a",
}, },
"mocha": { "mocha": {
"m3primary": "#f38ba8", "m3primary": "#f38ba8",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b46b84", "m3primaryContainer": "#b46b84",
"m3secondary": "#85596b", "m3secondary": "#85596b",
"m3surfaceTint": "#b46b84" "m3surfaceTint": "#b46b84",
} },
}, },
{ {
"id": "maroon", "id": "maroon",
@@ -265,29 +259,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#e87883", "m3primaryContainer": "#e87883",
"m3secondary": "#cfb7ba", "m3secondary": "#cfb7ba",
"m3surfaceTint": "#e87883" "m3surfaceTint": "#e87883",
}, },
"frappe": { "frappe": {
"m3primary": "#ea999c", "m3primary": "#ea999c",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#b27a83", "m3primaryContainer": "#b27a83",
"m3secondary": "#92626f", "m3secondary": "#92626f",
"m3surfaceTint": "#b27a83" "m3surfaceTint": "#b27a83",
}, },
"macchiato": { "macchiato": {
"m3primary": "#ee99a0", "m3primary": "#ee99a0",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b27781", "m3primaryContainer": "#b27781",
"m3secondary": "#8c5e6c", "m3secondary": "#8c5e6c",
"m3surfaceTint": "#b27781" "m3surfaceTint": "#b27781",
}, },
"mocha": { "mocha": {
"m3primary": "#eba0ac", "m3primary": "#eba0ac",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#ae7987", "m3primaryContainer": "#ae7987",
"m3secondary": "#895b6c", "m3secondary": "#895b6c",
"m3surfaceTint": "#ae7987" "m3surfaceTint": "#ae7987",
} },
}, },
{ {
"id": "peach", "id": "peach",
@@ -297,29 +291,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#f98e51", "m3primaryContainer": "#f98e51",
"m3secondary": "#c9b7ad", "m3secondary": "#c9b7ad",
"m3surfaceTint": "#f98e51" "m3surfaceTint": "#f98e51",
}, },
"frappe": { "frappe": {
"m3primary": "#ef9f76", "m3primary": "#ef9f76",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#b67f68", "m3primaryContainer": "#b67f68",
"m3secondary": "#8f6a5f", "m3secondary": "#8f6a5f",
"m3surfaceTint": "#b67f68" "m3surfaceTint": "#b67f68",
}, },
"macchiato": { "macchiato": {
"m3primary": "#f5a97f", "m3primary": "#f5a97f",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b7836a", "m3primaryContainer": "#b7836a",
"m3secondary": "#8c695e", "m3secondary": "#8c695e",
"m3surfaceTint": "#b7836a" "m3surfaceTint": "#b7836a",
}, },
"mocha": { "mocha": {
"m3primary": "#fab387", "m3primary": "#fab387",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b8876d", "m3primaryContainer": "#b8876d",
"m3secondary": "#8b6a5d", "m3secondary": "#8b6a5d",
"m3surfaceTint": "#b8876d" "m3surfaceTint": "#b8876d",
} },
}, },
{ {
"id": "yellow", "id": "yellow",
@@ -329,29 +323,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#e4ac5d", "m3primaryContainer": "#e4ac5d",
"m3secondary": "#c6baaa", "m3secondary": "#c6baaa",
"m3surfaceTint": "#e4ac5d" "m3surfaceTint": "#e4ac5d",
}, },
"frappe": { "frappe": {
"m3primary": "#e5c890", "m3primary": "#e5c890",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#af9b7a", "m3primaryContainer": "#af9b7a",
"m3secondary": "#948062", "m3secondary": "#948062",
"m3surfaceTint": "#af9b7a" "m3surfaceTint": "#af9b7a",
}, },
"macchiato": { "macchiato": {
"m3primary": "#eed49f", "m3primary": "#eed49f",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#b2a181", "m3primaryContainer": "#b2a181",
"m3secondary": "#947e62", "m3secondary": "#947e62",
"m3surfaceTint": "#b2a181" "m3surfaceTint": "#b2a181",
}, },
"mocha": { "mocha": {
"m3primary": "#f9e2af", "m3primary": "#f9e2af",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#b8a889", "m3primaryContainer": "#b8a889",
"m3secondary": "#978265", "m3secondary": "#978265",
"m3surfaceTint": "#b8a889" "m3surfaceTint": "#b8a889",
} },
}, },
{ {
"id": "green", "id": "green",
@@ -361,29 +355,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#74b867", "m3primaryContainer": "#74b867",
"m3secondary": "#9fbd9b", "m3secondary": "#9fbd9b",
"m3surfaceTint": "#74b867" "m3surfaceTint": "#74b867",
}, },
"frappe": { "frappe": {
"m3primary": "#a6d189", "m3primary": "#a6d189",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#83a275", "m3primaryContainer": "#83a275",
"m3secondary": "#648e5e", "m3secondary": "#648e5e",
"m3surfaceTint": "#83a275" "m3surfaceTint": "#83a275",
}, },
"macchiato": { "macchiato": {
"m3primary": "#a6da95", "m3primary": "#a6da95",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#80a57a", "m3primaryContainer": "#80a57a",
"m3secondary": "#5c8a61", "m3secondary": "#5c8a61",
"m3surfaceTint": "#80a57a" "m3surfaceTint": "#80a57a",
}, },
"mocha": { "mocha": {
"m3primary": "#a6e3a1", "m3primary": "#a6e3a1",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#7ea87f", "m3primaryContainer": "#7ea87f",
"m3secondary": "#5b8964", "m3secondary": "#5b8964",
"m3surfaceTint": "#7ea87f" "m3surfaceTint": "#7ea87f",
} },
}, },
{ {
"id": "teal", "id": "teal",
@@ -393,29 +387,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#57aeb4", "m3primaryContainer": "#57aeb4",
"m3secondary": "#93b4b7", "m3secondary": "#93b4b7",
"m3surfaceTint": "#57aeb4" "m3surfaceTint": "#57aeb4",
}, },
"frappe": { "frappe": {
"m3primary": "#81c8be", "m3primary": "#81c8be",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#699b9a", "m3primaryContainer": "#699b9a",
"m3secondary": "#588084", "m3secondary": "#588084",
"m3surfaceTint": "#699b9a" "m3surfaceTint": "#699b9a",
}, },
"macchiato": { "macchiato": {
"m3primary": "#8bd5ca", "m3primary": "#8bd5ca",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#6da29f", "m3primaryContainer": "#6da29f",
"m3secondary": "#577e83", "m3secondary": "#577e83",
"m3surfaceTint": "#6da29f" "m3surfaceTint": "#6da29f",
}, },
"mocha": { "mocha": {
"m3primary": "#94e2d5", "m3primary": "#94e2d5",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#71a8a4", "m3primaryContainer": "#71a8a4",
"m3secondary": "#588284", "m3secondary": "#588284",
"m3surfaceTint": "#71a8a4" "m3surfaceTint": "#71a8a4",
} },
}, },
{ {
"id": "sky", "id": "sky",
@@ -425,29 +419,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#4abcea", "m3primaryContainer": "#4abcea",
"m3secondary": "#a4b9c2", "m3secondary": "#a4b9c2",
"m3surfaceTint": "#4abcea" "m3surfaceTint": "#4abcea",
}, },
"frappe": { "frappe": {
"m3primary": "#99d1db", "m3primary": "#99d1db",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#79a2af", "m3primaryContainer": "#79a2af",
"m3secondary": "#628494", "m3secondary": "#628494",
"m3surfaceTint": "#79a2af" "m3surfaceTint": "#79a2af",
}, },
"macchiato": { "macchiato": {
"m3primary": "#91d7e3", "m3primary": "#91d7e3",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#71a3b0", "m3primaryContainer": "#71a3b0",
"m3secondary": "#5e7e8c", "m3secondary": "#5e7e8c",
"m3surfaceTint": "#71a3b0" "m3surfaceTint": "#71a3b0",
}, },
"mocha": { "mocha": {
"m3primary": "#89dceb", "m3primary": "#89dceb",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#69a3b3", "m3primaryContainer": "#69a3b3",
"m3secondary": "#5a7b88", "m3secondary": "#5a7b88",
"m3surfaceTint": "#69a3b3" "m3surfaceTint": "#69a3b3",
} },
}, },
{ {
"id": "sapphire", "id": "sapphire",
@@ -457,29 +451,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#5db8c8", "m3primaryContainer": "#5db8c8",
"m3secondary": "#9eb9be", "m3secondary": "#9eb9be",
"m3surfaceTint": "#5db8c8" "m3surfaceTint": "#5db8c8",
}, },
"frappe": { "frappe": {
"m3primary": "#85c1dc", "m3primary": "#85c1dc",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#6b96af", "m3primaryContainer": "#6b96af",
"m3secondary": "#5e7b8e", "m3secondary": "#5e7b8e",
"m3surfaceTint": "#6b96af" "m3surfaceTint": "#6b96af",
}, },
"macchiato": { "macchiato": {
"m3primary": "#7dc4e4", "m3primary": "#7dc4e4",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#6396b1", "m3primaryContainer": "#6396b1",
"m3secondary": "#5a7486", "m3secondary": "#5a7486",
"m3surfaceTint": "#6396b1" "m3surfaceTint": "#6396b1",
}, },
"mocha": { "mocha": {
"m3primary": "#74c7ec", "m3primary": "#74c7ec",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#5a95b4", "m3primaryContainer": "#5a95b4",
"m3secondary": "#567080", "m3secondary": "#567080",
"m3surfaceTint": "#5a95b4" "m3surfaceTint": "#5a95b4",
} },
}, },
{ {
"id": "blue", "id": "blue",
@@ -489,29 +483,29 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#5c90f5", "m3primaryContainer": "#5c90f5",
"m3secondary": "#b1bacb", "m3secondary": "#b1bacb",
"m3surfaceTint": "#5c90f5" "m3surfaceTint": "#5c90f5",
}, },
"frappe": { "frappe": {
"m3primary": "#8caaee", "m3primary": "#8caaee",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#7086bc", "m3primaryContainer": "#7086bc",
"m3secondary": "#637195", "m3secondary": "#637195",
"m3surfaceTint": "#7086bc" "m3surfaceTint": "#7086bc",
}, },
"macchiato": { "macchiato": {
"m3primary": "#8aadf4", "m3primary": "#8aadf4",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#6c85bc", "m3primaryContainer": "#6c85bc",
"m3secondary": "#5f6d8f", "m3secondary": "#5f6d8f",
"m3surfaceTint": "#6c85bc" "m3surfaceTint": "#6c85bc",
}, },
"mocha": { "mocha": {
"m3primary": "#89b4fa", "m3primary": "#89b4fa",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#6987bd", "m3primaryContainer": "#6987bd",
"m3secondary": "#5d6c8b", "m3secondary": "#5d6c8b",
"m3surfaceTint": "#6987bd" "m3surfaceTint": "#6987bd",
} },
}, },
{ {
"id": "lavender", "id": "lavender",
@@ -521,30 +515,30 @@ _data = {
"m3primaryText": "#eff1f5", "m3primaryText": "#eff1f5",
"m3primaryContainer": "#97a7fb", "m3primaryContainer": "#97a7fb",
"m3secondary": "#cdcfdd", "m3secondary": "#cdcfdd",
"m3surfaceTint": "#97a7fb" "m3surfaceTint": "#97a7fb",
}, },
"frappe": { "frappe": {
"m3primary": "#babbf1", "m3primary": "#babbf1",
"m3primaryText": "#303446", "m3primaryText": "#303446",
"m3primaryContainer": "#9192be", "m3primaryContainer": "#9192be",
"m3secondary": "#7175a1", "m3secondary": "#7175a1",
"m3surfaceTint": "#9192be" "m3surfaceTint": "#9192be",
}, },
"macchiato": { "macchiato": {
"m3primary": "#b7bdf8", "m3primary": "#b7bdf8",
"m3primaryText": "#24273a", "m3primaryText": "#24273a",
"m3primaryContainer": "#8b91bf", "m3primaryContainer": "#8b91bf",
"m3secondary": "#6b709d", "m3secondary": "#6b709d",
"m3surfaceTint": "#8b91bf" "m3surfaceTint": "#8b91bf",
}, },
"mocha": { "mocha": {
"m3primary": "#b4befe", "m3primary": "#b4befe",
"m3primaryText": "#1e1e2e", "m3primaryText": "#1e1e2e",
"m3primaryContainer": "#878ec0", "m3primaryContainer": "#878ec0",
"m3secondary": "#676d99", "m3secondary": "#676d99",
"m3surfaceTint": "#878ec0" "m3surfaceTint": "#878ec0",
} },
} },
] ],
} },
} }
+12 -10
View File
@@ -1,11 +1,13 @@
import json # import json
import typer # import typer
from zshell.assets.schemes.catppuccin import catppuccin # from zshell.assets.schemes.catppuccin import catppuccin
#
# app = typer.Typer()
#
# SCHEMES = catppuccin.variants.flavors
#
#
# @app.command()
# def set():
app = typer.Typer() # TODO: Currently unsused
SCHEMES = catppuccin.variants.flavors
@app.command()
def set():
+15 -13
View File
@@ -1,14 +1,16 @@
import typer # import typer
import subprocess # 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 # TODO: Currently unused
app = typer.Typer()
RECORDER = "gpu-screen-recorder"
HOME = str(os.getenv("HOME"))
CONFIG = Path(HOME + "/.config/zshell/config.json")
@app.command()
def start():
+28 -32
View File
@@ -23,64 +23,71 @@ app = typer.Typer()
@app.command() @app.command()
def generate( def generate(
# image inputs (optional - used for image mode) # image inputs (optional - used for image mode)
image_path: Optional[Path] = typer.Option( image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."),
None, help="Path to source image. Required for image mode."),
scheme: Optional[str] = typer.Option( 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 inputs (optional - used for preset mode)
preset: Optional[str] = typer.Option( preset: Optional[str] = typer.Option(
None, help="Name of a premade scheme in this format: <preset_name>:<preset_flavor>"), 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)."), mode: Optional[str] = typer.Option(None, help="Mode of the preset scheme (dark or light)."),
): ):
HOME = str(os.getenv("HOME")) HOME = str(os.getenv("HOME"))
OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json") OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json")
SEQ_STATE = Path(HOME + "/.local/state/zshell/sequences.txt") SEQ_STATE = Path(HOME + "/.local/state/zshell/sequences.txt")
THUMB_PATH = Path(HOME + THUMB_PATH = Path(HOME + "/.cache/zshell/imagecache/thumbnail.jpg")
"/.cache/zshell/imagecache/thumbnail.jpg") WALL_DIR_PATH = Path(HOME + "/.local/state/zshell/wallpaper_path.json")
WALL_DIR_PATH = Path(HOME +
"/.local/state/zshell/wallpaper_path.json")
TEMPLATE_DIR = Path(HOME + "/.config/zshell/templates") TEMPLATE_DIR = Path(HOME + "/.config/zshell/templates")
WALL_PATH = Path() WALL_PATH = Path()
CONFIG = Path(HOME + "/.config/zshell/config.json") CONFIG = Path(HOME + "/.config/zshell/config.json")
if preset is not None and image_path is not None: if preset is not None and image_path is not None:
raise typer.BadParameter( raise typer.BadParameter("Use either --image-path or --preset, not both.")
"Use either --image-path or --preset, not both.")
def get_scheme_class(scheme_name: str): def get_scheme_class(scheme_name: str):
match scheme_name: match scheme_name:
case "fruit-salad": case "fruit-salad":
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad
return SchemeFruitSalad return SchemeFruitSalad
case "expressive": case "expressive":
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive from materialyoucolor.scheme.scheme_expressive import SchemeExpressive
return SchemeExpressive return SchemeExpressive
case "monochrome": case "monochrome":
from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome
return SchemeMonochrome return SchemeMonochrome
case "rainbow": case "rainbow":
from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow
return SchemeRainbow return SchemeRainbow
case "tonal-spot": case "tonal-spot":
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
return SchemeTonalSpot return SchemeTonalSpot
case "neutral": case "neutral":
from materialyoucolor.scheme.scheme_neutral import SchemeNeutral from materialyoucolor.scheme.scheme_neutral import SchemeNeutral
return SchemeNeutral return SchemeNeutral
case "fidelity": case "fidelity":
from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity
return SchemeFidelity return SchemeFidelity
case "content": case "content":
from materialyoucolor.scheme.scheme_content import SchemeContent from materialyoucolor.scheme.scheme_content import SchemeContent
return SchemeContent return SchemeContent
case "vibrant": case "vibrant":
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
return SchemeVibrant return SchemeVibrant
case _: case _:
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad
return SchemeFruitSalad return SchemeFruitSalad
def hex_to_hct(hex_color: str) -> Hct: 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: def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct:
diff = difference_degrees(from_hct.hue, to_hct.hue) diff = difference_degrees(from_hct.hue, to_hct.hue)
rotation = min(diff * 0.8, 100) rotation = min(diff * 0.8, 100)
output_hue = sanitize_degrees_double( output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue))
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))) tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost)))
return Hct.from_hct(output_hue, from_hct.chroma, tone) return Hct.from_hct(output_hue, from_hct.chroma, tone)
def terminal_palette( def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]:
colors: dict[str, str], mode: str, variant: str
) -> dict[str, str]:
light = mode.lower() == "light" light = mode.lower() == "light"
key_hex = ( key_hex = (
@@ -307,7 +309,7 @@ def generate(
"seed": seed.to_int(), "seed": seed.to_int(),
"flavor": flavor, "flavor": flavor,
"variant": variant, "variant": variant,
"colors": colors "colors": colors,
} }
for k, v in colors.items(): for k, v in colors.items():
@@ -349,7 +351,7 @@ def generate(
def tmux_wrap_sequences(seq: str) -> str: def tmux_wrap_sequences(seq: str) -> str:
ESC = "\x1b" 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]: def parse_output_directive(first_line: str) -> Optional[Path]:
s = first_line.strip() s = first_line.strip()
@@ -406,8 +408,7 @@ def generate(
template = env.from_string(body) template = env.from_string(body)
text = template.render(**context) text = template.render(**context)
except Exception as e: except Exception as e:
raise RuntimeError( raise RuntimeError(f"Template render failed for '{rel}': {e}") from e
f"Template render failed for '{rel}': {e}") from e
out_path.write_text(text, encoding="utf-8") out_path.write_text(text, encoding="utf-8")
@@ -435,18 +436,13 @@ def generate(
try: try:
return PRESETS[name].primary return PRESETS[name].primary
except KeyError: except KeyError:
raise typer.BadParameter( raise typer.BadParameter(f"Preset '{name}' not found. Available presets: {', '.join(PRESETS.keys())}")
f"Preset '{name}' not found. Available presets: {', '.join(PRESETS.keys())}")
def generate_color_scheme(seed: Hct, mode: str, scheme_class) -> dict[str, str]: def generate_color_scheme(seed: Hct, mode: str, scheme_class) -> dict[str, str]:
is_dark = mode.lower() == "dark" is_dark = mode.lower() == "dark"
scheme = scheme_class( scheme = scheme_class(seed, is_dark, 0.0)
seed,
is_dark,
0.0
)
color_dict = {} color_dict = {}
for color in vars(MaterialDynamicColors).keys(): for color in vars(MaterialDynamicColors).keys():
@@ -499,7 +495,7 @@ def generate(
"mode": effective_mode, "mode": effective_mode,
"variant": scheme, "variant": scheme,
"colors": colors, "colors": colors,
"seed": seed.to_int() "seed": seed.to_int(),
} }
if TEMPLATE_DIR is not None: if TEMPLATE_DIR is not None:
@@ -511,7 +507,7 @@ def generate(
wallpaper_path=wp, wallpaper_path=wp,
name=name, name=name,
flavor=flavor, flavor=flavor,
variant=scheme variant=scheme,
) )
rendered = render_all_templates( rendered = render_all_templates(
+2 -4
View File
@@ -8,11 +8,9 @@ app = typer.Typer()
@app.command() @app.command()
def start(): def start():
subprocess.run(args + ["ipc"] + ["call"] + subprocess.run(args + ["ipc"] + ["call"] + ["picker"] + ["open"], check=True)
["picker"] + ["open"], check=True)
@app.command() @app.command()
def start_freeze(): def start_freeze():
subprocess.run(args + ["ipc"] + ["call"] + subprocess.run(args + ["ipc"] + ["call"] + ["picker"] + ["openFreeze"], check=True)
["picker"] + ["openFreeze"], check=True)
+1 -2
View File
@@ -33,5 +33,4 @@ def lock():
@app.command() @app.command()
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)): def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
subprocess.run(args + ["ipc"] + ["call"] + [target] + subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + method_args, check=True)
[method] + method_args, check=True)
+12 -13
View File
@@ -12,29 +12,28 @@ app = typer.Typer()
@app.command() @app.command()
def set(wallpaper: Path): def set(wallpaper: Path):
subprocess.run(args + ["ipc"] + ["call"] + subprocess.run(args + ["ipc"] + ["call"] + ["wallpaper"] + ["set"] + [wallpaper], check=True)
["wallpaper"] + ["set"] + [wallpaper], check=True)
@app.command() @app.command()
def lockscreen( def lockscreen(
input_image: Annotated[ input_image: Annotated[
Path, Path,
typer.Option(), typer.Option(),
], ],
output_path: Annotated[ output_path: Annotated[
Path, Path,
typer.Option(), typer.Option(),
], ],
blur_amount: int = 20 blur_amount: int = 20,
): ):
img = Image.open(input_image) img = Image.open(input_image)
size = img.size size = img.size
if (blur_amount == 0): if blur_amount == 0:
img.save(output_path, "PNG") img.save(output_path, "PNG")
return 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) img = img.resize((size[0] // 2, size[1] // 2), Image.NEAREST)
else: else:
img = img.resize((size[0] // 4, size[1] // 4), Image.NEAREST) 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
+95 -95
View File
@@ -3,141 +3,141 @@
// Translated to Js from Cython with an LLM and reviewed // Translated to Js from Cython with an LLM and reviewed
function min3(a, b, c) { function min3(a, b, c) {
return a < b && a < c ? a : b < c ? b : c; return a < b && a < c ? a : b < c ? b : c;
} }
function max3(a, b, c) { function max3(a, b, c) {
return a > b && a > c ? a : b > c ? b : c; return a > b && a > c ? a : b > c ? b : c;
} }
function min2(a, b) { function min2(a, b) {
return a < b ? a : b; return a < b ? a : b;
} }
function max2(a, b) { function max2(a, b) {
return a > b ? a : b; return a > b ? a : b;
} }
function levenshteinDistance(s1, s2) { function levenshteinDistance(s1, s2) {
let len1 = s1.length; let len1 = s1.length;
let len2 = s2.length; let len2 = s2.length;
if (len1 === 0) return len2; if (len1 === 0) return len2;
if (len2 === 0) return len1; if (len2 === 0) return len1;
if (len2 > len1) { if (len2 > len1) {
[s1, s2] = [s2, s1]; [s1, s2] = [s2, s1];
[len1, len2] = [len2, len1]; [len1, len2] = [len2, len1];
}
let prev = new Array(len2 + 1);
let curr = new Array(len2 + 1);
for (let j = 0; j <= len2; j++) {
prev[j] = j;
}
for (let i = 1; i <= len1; i++) {
curr[0] = i;
for (let j = 1; j <= len2; j++) {
let cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
} }
[prev, curr] = [curr, prev];
}
return prev[len2]; let prev = new Array(len2 + 1);
let curr = new Array(len2 + 1);
for (let j = 0; j <= len2; j++) {
prev[j] = j;
}
for (let i = 1; i <= len1; i++) {
curr[0] = i;
for (let j = 1; j <= len2; j++) {
let cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
}
[prev, curr] = [curr, prev];
}
return prev[len2];
} }
function partialRatio(shortS, longS) { function partialRatio(shortS, longS) {
let lenS = shortS.length; let lenS = shortS.length;
let lenL = longS.length; let lenL = longS.length;
let best = 0.0; let best = 0.0;
if (lenS === 0) return 1.0; if (lenS === 0) return 1.0;
for (let i = 0; i <= lenL - lenS; i++) { for (let i = 0; i <= lenL - lenS; i++) {
let sub = longS.slice(i, i + lenS); let sub = longS.slice(i, i + lenS);
let dist = levenshteinDistance(shortS, sub); let dist = levenshteinDistance(shortS, sub);
let score = 1.0 - dist / lenS; let score = 1.0 - dist / lenS;
if (score > best) best = score; if (score > best) best = score;
} }
return best; return best;
} }
function computeScore(s1, s2) { function computeScore(s1, s2) {
if (s1 === s2) return 1.0; if (s1 === s2) return 1.0;
let dist = levenshteinDistance(s1, s2); let dist = levenshteinDistance(s1, s2);
let maxLen = max2(s1.length, s2.length); let maxLen = max2(s1.length, s2.length);
if (maxLen === 0) return 1.0; if (maxLen === 0) return 1.0;
let full = 1.0 - dist / maxLen; let full = 1.0 - dist / maxLen;
let part = let part =
s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1); s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
let score = 0.85 * full + 0.15 * part; let score = 0.85 * full + 0.15 * part;
if (s1 && s2 && s1[0] !== s2[0]) { if (s1 && s2 && s1[0] !== s2[0]) {
score -= 0.05; score -= 0.05;
}
let lenDiff = Math.abs(s1.length - s2.length);
if (lenDiff >= 3) {
score -= (0.05 * lenDiff) / maxLen;
}
let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
} }
}
score += 0.02 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) { let lenDiff = Math.abs(s1.length - s2.length);
score += 0.06; if (lenDiff >= 3) {
} score -= (0.05 * lenDiff) / maxLen;
}
return Math.max(0.0, Math.min(1.0, score)); let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
}
}
score += 0.02 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) {
score += 0.06;
}
return Math.max(0.0, Math.min(1.0, score));
} }
function computeTextMatchScore(s1, s2) { function computeTextMatchScore(s1, s2) {
if (s1 === s2) return 1.0; if (s1 === s2) return 1.0;
let dist = levenshteinDistance(s1, s2); let dist = levenshteinDistance(s1, s2);
let maxLen = max2(s1.length, s2.length); let maxLen = max2(s1.length, s2.length);
if (maxLen === 0) return 1.0; if (maxLen === 0) return 1.0;
let full = 1.0 - dist / maxLen; let full = 1.0 - dist / maxLen;
let part = let part =
s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1); s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
let score = 0.4 * full + 0.6 * part; let score = 0.4 * full + 0.6 * part;
let lenDiff = Math.abs(s1.length - s2.length); let lenDiff = Math.abs(s1.length - s2.length);
if (lenDiff >= 10) { if (lenDiff >= 10) {
score -= (0.02 * lenDiff) / maxLen; score -= (0.02 * lenDiff) / maxLen;
}
let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
} }
}
score += 0.01 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) { let commonPrefixLen = 0;
score += 0.2; let minLen = min2(s1.length, s2.length);
} for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
}
}
score += 0.01 * commonPrefixLen;
return Math.max(0.0, Math.min(1.0, score)); if (s1.includes(s2) || s2.includes(s1)) {
score += 0.2;
}
return Math.max(0.0, Math.min(1.0, score));
} }
+16 -8
View File
@@ -39,8 +39,10 @@ pub fn apply_rounded_corners(img: RgbaImage, radius: f32) -> RgbaImage {
); );
let mut pixmap = rgba_image_to_pixmap(&img); let mut pixmap = rgba_image_to_pixmap(&img);
let mut dst_paint = PixmapPaint::default(); let dst_paint = PixmapPaint {
dst_paint.blend_mode = BlendMode::DestinationIn; blend_mode: BlendMode::DestinationIn,
..Default::default()
};
pixmap.draw_pixmap(0, 0, mask.as_ref(), &dst_paint, Transform::identity(), None); pixmap.draw_pixmap(0, 0, mask.as_ref(), &dst_paint, Transform::identity(), None);
pixmap_to_rgba_image(pixmap) pixmap_to_rgba_image(pixmap)
} }
@@ -69,8 +71,10 @@ pub fn apply_drop_shadow(
let shadow_x = (extra_left as f32 + offset_x) as i32; let shadow_x = (extra_left as f32 + offset_x) as i32;
let shadow_y = (extra_top as f32 + offset_y) as i32; let shadow_y = (extra_top as f32 + offset_y) as i32;
let mut sp = PixmapPaint::default(); let sp = PixmapPaint {
sp.blend_mode = BlendMode::Source; blend_mode: BlendMode::Source,
..Default::default()
};
shadow_pixmap.draw_pixmap( shadow_pixmap.draw_pixmap(
shadow_x, shadow_x,
shadow_y, shadow_y,
@@ -87,8 +91,10 @@ pub fn apply_drop_shadow(
let blurred_pixmap = rgba_image_to_pixmap(&blurred); let blurred_pixmap = rgba_image_to_pixmap(&blurred);
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap"); let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap");
let mut p = PixmapPaint::default(); let p = PixmapPaint {
p.blend_mode = BlendMode::Source; blend_mode: BlendMode::Source,
..Default::default()
};
canvas.draw_pixmap( canvas.draw_pixmap(
0, 0,
0, 0,
@@ -98,8 +104,10 @@ pub fn apply_drop_shadow(
None, None,
); );
let mut p2 = PixmapPaint::default(); let p2 = PixmapPaint {
p2.blend_mode = BlendMode::SourceOver; blend_mode: BlendMode::SourceOver,
..Default::default()
};
canvas.draw_pixmap( canvas.draw_pixmap(
extra_left as i32, extra_left as i32,
extra_top as i32, extra_top as i32,
+1 -1
View File
@@ -1,7 +1,7 @@
mod config; mod config;
mod effects; mod effects;
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use std::io::Write as _; use std::io::Write as _;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};