40 Commits
Author SHA1 Message Date
zach 0fc1a123fa mute cmake install output 2026-07-08 23:08:42 +02:00
zach 672cb07547 feat: import M3Shapes, M3 loading indicator and weather + resources + media revamp in lockscreen 2026-07-08 22:53:19 +02:00
zach e3b2d33a11 fix: fallback to python wheel if Nuitka is not available 2026-07-08 18:32:21 +02:00
zach 2a40696292 Merge pull request 'chore(ci-image): fix tld in ci-image workflow + additional workflow improvements' (#137) from update-cibuild-tld into main
Reviewed-on: #137
Reviewed-by: zach <zach@brohn.se>
2026-07-08 18:25:10 +02:00
zach 80ee714e91 Merge branch 'main' into update-cibuild-tld
C++ / fmt (pull_request) Successful in 4s
Python / fmt (pull_request) Successful in 58s
JS/TS / lint (pull_request) Successful in 1m14s
JS/TS / fmt (pull_request) Failing after 1m32s
Python / lint (pull_request) Failing after 1m55s
C++ / clang-tidy (pull_request) Successful in 2m55s
C++ / build (pull_request) Successful in 2m58s
Rust / fmt (pull_request) Successful in 1m21s
Python / test (pull_request) Successful in 2m25s
Rust / clippy (pull_request) Successful in 1m31s
Rust / build (pull_request) Successful in 3m5s
Python / buildcheck (pull_request) Successful in 3m25s
2026-07-08 17:50:24 +02:00
zach 2c572c3250 feat: animations in settings search results 2026-07-08 17:49:26 +02:00
AramJonghu 0912a8e40c hotfix(): Forgot to change ruff package as dep
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 59s
C++ / build (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 1m12s
Rust / build (pull_request) Successful in 1m58s
Python / buildcheck (pull_request) Successful in 2m44s
Rust / clippy (pull_request) Successful in 1m57s
C++ / clang-tidy (pull_request) Successful in 3m37s
2026-07-08 17:30:46 +02:00
AramJonghu 0d17621180 chore(py formatter): Revert from autopep8 -> ruff after config update
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Failing after 26s
Python / lint (pull_request) Successful in 29s
Python / test (pull_request) Successful in 1m3s
Rust / fmt (pull_request) Successful in 1m11s
C++ / build (pull_request) Successful in 1m53s
Rust / build (pull_request) Successful in 1m56s
Rust / clippy (pull_request) Successful in 1m45s
Python / buildcheck (pull_request) Successful in 2m44s
C++ / clang-tidy (pull_request) Successful in 3m49s
2026-07-08 17:28:25 +02:00
zach 0ae6d6fc55 chore: format test files
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 19s
Python / lint (pull_request) Successful in 32s
Python / fmt (pull_request) Failing after 37s
Python / test (pull_request) Successful in 54s
C++ / build (pull_request) Successful in 1m57s
Rust / fmt (pull_request) Successful in 1m16s
Rust / build (pull_request) Successful in 1m56s
Python / buildcheck (pull_request) Successful in 2m44s
Rust / clippy (pull_request) Successful in 1m56s
C++ / clang-tidy (pull_request) Successful in 3m46s
2026-07-08 17:01:25 +02:00
zach 9300da258e chore: python code formatted with ruff, ruff formatter + linter, pyproject.toml now in root
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 18s
Python / lint (pull_request) Failing after 32s
Python / fmt (pull_request) Failing after 37s
Python / test (pull_request) Successful in 52s
C++ / build (pull_request) Successful in 1m56s
Rust / fmt (pull_request) Successful in 1m22s
Rust / build (pull_request) Successful in 2m5s
Python / buildcheck (pull_request) Successful in 2m39s
Rust / clippy (pull_request) Successful in 2m1s
C++ / clang-tidy (pull_request) Successful in 3m46s
2026-07-08 16:54:41 +02:00
zach 298cbbf424 Merge branch 'main' into update-cibuild-tld
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Failing after 30s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 54s
C++ / build (pull_request) Successful in 1m47s
Rust / fmt (pull_request) Successful in 1m16s
Rust / build (pull_request) Successful in 2m0s
Python / buildcheck (pull_request) Successful in 2m41s
Rust / clippy (pull_request) Successful in 1m54s
C++ / clang-tidy (pull_request) Successful in 3m41s
2026-07-08 16:18:15 +02:00
zach b19b630a77 better ddcutil handling, revert to using onMoved brightness slider, fix osd region 2026-07-08 16:17:37 +02:00
AramJonghu 75f0f2071d fix(ci): include zshell python package for buildcheck
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 18s
Python / fmt (pull_request) Failing after 32s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 52s
Rust / fmt (pull_request) Successful in 1m11s
C++ / build (pull_request) Successful in 1m57s
Rust / build (pull_request) Successful in 1m49s
Python / buildcheck (pull_request) Successful in 2m39s
Rust / clippy (pull_request) Successful in 1m45s
C++ / clang-tidy (pull_request) Successful in 3m43s
2026-07-08 14:52:42 +02:00
AramJonghu bfea718547 fix(ci): clang-format pass echo, nuitka full package compile
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Failing after 34s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 51s
C++ / build (pull_request) Successful in 1m37s
Rust / fmt (pull_request) Successful in 1m8s
Rust / build (pull_request) Successful in 1m52s
Python / buildcheck (pull_request) Successful in 2m37s
Rust / clippy (pull_request) Successful in 1m55s
C++ / clang-tidy (pull_request) Successful in 3m37s
2026-07-08 14:49:02 +02:00
AramJonghu 5505ac19ba refactor(ci): add python workflow, split cpp jobs, add clang-format
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 17s
Python / fmt (pull_request) Failing after 29s
Python / lint (pull_request) Successful in 35s
Python / test (pull_request) Successful in 56s
C++ / build (pull_request) Successful in 1m49s
Rust / fmt (pull_request) Successful in 1m9s
Rust / build (pull_request) Successful in 1m51s
Python / buildcheck (pull_request) Successful in 2m31s
Rust / clippy (pull_request) Successful in 1m47s
C++ / clang-tidy (pull_request) Successful in 3m29s
2026-07-08 14:37:35 +02:00
AramJonghu 9d2dc281ec refactor(ci): restructure workflows to multi-job per language
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 14s
Rust / fmt (pull_request) Successful in 1m30s
Rust / build (pull_request) Successful in 1m53s
Rust / clippy (pull_request) Successful in 2m20s
C++ / build (pull_request) Successful in 2m54s
2026-07-08 14:28:51 +02:00
AramJonghu 05c8851143 Merge branch 'main' into update-cibuild-tld
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 22s
Python / lint-format (pull_request) Successful in 27s
Python / test (pull_request) Successful in 50s
C++ / build (pull_request) Successful in 2m31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m34s
2026-07-08 13:51:16 +02:00
zach 1cbe9a9f8a Merge pull request 'Add ahead-of-time compilation for cli and optimize thumbnail caching' (#136) from feat/aot-compilation-cli into main
Reviewed-on: #136
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
2026-07-08 13:41:18 +02:00
AramJonghu 71b25bff49 chore(ci-image): fix tld in ci-image workflow
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 45s
Python / lint-format (pull_request) Successful in 52s
Python / test (pull_request) Successful in 44s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m50s
C++ / build (pull_request) Successful in 3m40s
2026-07-08 13:21:19 +02:00
zach 87433334c7 fix cmake compilation of python code
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 41s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m41s
C++ / build (pull_request) Successful in 2m27s
2026-07-08 13:01:53 +02:00
zach 6d5609fe5b Add ahead-of-time compilation for cli and optimize thumbnail caching
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 46s
Python / lint-format (pull_request) Successful in 49s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m42s
C++ / build (pull_request) Successful in 3m7s
2026-07-08 12:23:45 +02:00
zach ffefd3e202 clip settings at bar bounds 2026-07-07 18:47:48 +02:00
zach 2db6a57c0b fix: shift background in accordance with content 2026-07-06 19:40:57 +02:00
zach 0c5871e108 add burn-in prevention for lock screen 2026-07-06 14:12:07 +02:00
zach 6ad9fe5e07 settings highlight optimized 2026-07-05 18:01:52 +02:00
zach 8c11aead18 fix: osd stays open when hovered 2026-07-04 15:54:44 +02:00
zach d013be0f9a fix: include spacing for expand icon tray menu 2026-07-03 17:40:45 +02:00
zach 69774e7759 feat: greeter support hyprland lua 2026-07-03 15:52:27 +02:00
zach cdf66bff23 fix: better width + height calculations for tray menu popouts 2026-07-03 15:02:00 +02:00
zach 562770595f fix: add generic gpu vram support 2026-07-03 12:28:54 +02:00
zach 9126b04b10 fix: close tray menus when hovering bar + showOnHover enabled 2026-07-02 18:50:14 +02:00
zach 55dab8f7ce HyprlandFocusGrab on traymenu stackdepth > 1 2026-07-02 16:15:18 +02:00
zach c62695bfff chore: cleanup more unused files/imports, use new buttonrow for polkit 2026-07-02 13:35:17 +02:00
zach 2b06a33508 chore: cleanup unused files/imports 2026-07-02 12:49:47 +02:00
zach 4d731c5a65 feat: add option for recoloring tray icons + show popout on hover 2026-07-02 11:33:43 +02:00
zach 53836aec1a fix: anim property for record tool 2026-07-01 22:35:58 +02:00
zach 355106cb2b add radius anim to search results 2026-06-30 22:40:08 +02:00
zach 865b5bda53 chore: cleanup comments 2026-06-30 22:27:37 +02:00
zach 2b89c8e4a1 implement search bar functionality in settings 2026-06-30 22:20:48 +02:00
zach 61019204d8 formatter 2026-06-30 15:35:25 +02:00
135 changed files with 4341 additions and 6321 deletions
+3 -3
View File
@@ -2,7 +2,7 @@ name: Rebuild CI Image
on:
schedule:
- cron: '0 6 * * 1'
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
@@ -11,7 +11,7 @@ jobs:
container:
image: node:26-alpine
env:
IMAGE: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
IMAGE: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
@@ -21,7 +21,7 @@ jobs:
run: apk add --no-cache docker-cli
- name: Login to registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --username aramjonghu --password-stdin
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
- name: Build image
run: docker build -t "$IMAGE" -f ci/Dockerfile .
+34 -1
View File
@@ -4,10 +4,43 @@ on:
pull_request:
jobs:
fmt:
runs-on: alpine
container:
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Format check
run: |
find . \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
-not -path './build/*' \
-exec clang-format -i --style=file {} +
git diff --exit-code && echo "clang-format: passed"
build:
runs-on: alpine
container:
image: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
- name: Build
run: ninja -C build
clang-tidy:
runs-on: alpine
container:
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
@@ -1,10 +1,10 @@
name: Lint & Format (JS/TS)
name: JS/TS
on:
pull_request:
jobs:
lint-format:
fmt:
runs-on: alpine
container: node:26-alpine
@@ -18,7 +18,6 @@ jobs:
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
@@ -26,6 +25,19 @@ jobs:
echo "No JS/TS files found"
fi
lint:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git
- 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
-85
View File
@@ -1,85 +0,0 @@
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
- id: format-check
name: Format check
continue-on-error: true
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \
{ echo "$manifest: needs formatting"; 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
- id: clippy
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
- name: Check results
if: always()
run: |
if [ "${{ steps.format-check.outcome }}" = "failure" ] || [ "${{ steps.clippy.outcome }}" = "failure" ]; then
echo "One or more checks failed"
exit 1
fi
echo "All checks passed"
@@ -4,7 +4,7 @@ on:
pull_request:
jobs:
lint-format:
fmt:
runs-on: alpine
container: node:26-alpine
@@ -23,11 +23,28 @@ jobs:
pip install --no-cache-dir ruff
- name: Format check
continue-on-error: true
run: |
. .venv/bin/activate
ruff format --check .
lint:
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: Lint
run: |
. .venv/bin/activate
@@ -63,3 +80,30 @@ jobs:
. .venv/bin/activate
cd cli
python -m pytest tests/ -v
buildcheck:
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 \
build-base \
python3-dev \
gcc \
g++
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir nuitka
- name: Nuitka module check
run: |
. .venv/bin/activate
nuitka --module --include-package=zshell cli/src/zshell/
+152
View File
@@ -0,0 +1,152 @@
name: Rust
on:
pull_request:
jobs:
build:
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-build-${{ runner.os }}-${{ 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
- name: Cargo check
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
for manifest in $(find . -name "Cargo.toml"); do
cargo check --manifest-path "$manifest" && \
echo "$manifest: check passed" || \
{ echo "$manifest: check failed"; exit 1; }
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
fmt:
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-fmt-${{ runner.os }}-${{ 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
- name: Format check
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \
{ echo "$manifest: needs formatting"; 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
clippy:
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-clippy-${{ runner.os }}-${{ 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 \
rust-clippy
- 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
+118 -2
View File
@@ -36,13 +36,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(ENABLE_MODULES "plugin;shell" CACHE STRING "Modules to build/install")
set(ENABLE_MODULES "plugin;shell;m3shapes" CACHE STRING "Modules to build/install")
set(INSTALL_LIBDIR "usr/lib/ZShell" CACHE STRING "Library install dir")
set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir")
set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir")
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
set(CMAKE_INSTALL_MESSAGE NEVER)
add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
@@ -52,9 +54,104 @@ add_compile_options(
)
if("shell" IN_LIST ENABLE_MODULES)
# Build settings index
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(SETTINGS_INDEX_JSON "${CMAKE_BINARY_DIR}/settings-index.json")
execute_process(
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/scripts/build-settings-index.py"
"${CMAKE_SOURCE_DIR}/Modules/Settings"
"${SETTINGS_INDEX_JSON}"
RESULT_VARIABLE SETTINGS_INDEX_RESULT
)
if(NOT SETTINGS_INDEX_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to build settings search index")
endif()
# Nuitka compilation
set(ZSHELL_CLI_BUILD_DIR "${CMAKE_BINARY_DIR}/zshell-cli")
set(ZSHELL_CLI_DIST "${ZSHELL_CLI_BUILD_DIR}/zshell.dist")
set(ZSHELL_CLI_SRC "${CMAKE_SOURCE_DIR}/cli/src/zshell")
find_program(NUITKA_EXECUTABLE nuitka)
if(NUITKA_EXECUTABLE)
file(GLOB_RECURSE ZSHELL_CLI_SOURCES CONFIGURE_DEPENDS
"${ZSHELL_CLI_SRC}/*.py"
)
file(GLOB_RECURSE ZSHELL_CLI_ASSETS CONFIGURE_DEPENDS
"${ZSHELL_CLI_SRC}/assets/*"
)
add_custom_command(
OUTPUT "${ZSHELL_CLI_DIST}/zshell-cli"
COMMAND ${CMAKE_COMMAND} -E make_directory "${ZSHELL_CLI_BUILD_DIR}"
COMMAND ${CMAKE_COMMAND} -E rm -rf "${ZSHELL_CLI_DIST}"
COMMAND
${NUITKA_EXECUTABLE}
--standalone
--include-data-dir=${CMAKE_SOURCE_DIR}/cli/src/zshell/assets=zshell/assets
--output-dir=${ZSHELL_CLI_BUILD_DIR}
--output-filename=zshell-cli
${CMAKE_SOURCE_DIR}/cli/src/zshell/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/cli
DEPENDS ${ZSHELL_CLI_SOURCES} ${ZSHELL_CLI_ASSETS}
)
add_custom_target(zshell-cli ALL DEPENDS "${ZSHELL_CLI_DIST}/zshell-cli")
install(PROGRAMS "${ZSHELL_CLI_DIST}/zshell-cli" DESTINATION "${INSTALL_LIBDIR}/zshell-cli")
install(DIRECTORY "${ZSHELL_CLI_DIST}/" DESTINATION "${INSTALL_LIBDIR}/zshell-cli" PATTERN "zshell-cli" EXCLUDE)
configure_file(
"${CMAKE_SOURCE_DIR}/Plugins/cmake/zshell-cli.cmake.in"
"${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli.cmake"
@ONLY
)
install(SCRIPT "${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli.cmake")
else()
message(STATUS "Nuitka not found, building zshell-cli as a Python wheel instead")
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(ZSHELL_CLI_DIR "${CMAKE_SOURCE_DIR}")
set(ZSHELL_CLI_DIST_DIR "${ZSHELL_CLI_DIR}/dist")
set(ZSHELL_CLI_WHEEL_STAMP "${CMAKE_BINARY_DIR}/zshell-cli/wheel.stamp")
file(GLOB_RECURSE ZSHELL_CLI_SOURCES CONFIGURE_DEPENDS
"${ZSHELL_CLI_DIR}/src/zshell/*.py"
)
file(GLOB_RECURSE ZSHELL_CLI_ASSETS CONFIGURE_DEPENDS
"${ZSHELL_CLI_DIR}/src/zshell/assets/*"
)
add_custom_command(
OUTPUT "${ZSHELL_CLI_WHEEL_STAMP}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/zshell-cli"
COMMAND ${CMAKE_COMMAND} -E rm -rf "${ZSHELL_CLI_DIST_DIR}"
COMMAND ${Python3_EXECUTABLE} -m build --wheel --no-isolation
COMMAND ${CMAKE_COMMAND} -E touch "${ZSHELL_CLI_WHEEL_STAMP}"
WORKING_DIRECTORY "${ZSHELL_CLI_DIR}"
DEPENDS ${ZSHELL_CLI_SOURCES} ${ZSHELL_CLI_ASSETS}
COMMENT "Building zshell-cli python wheel"
)
add_custom_target(zshell-cli ALL DEPENDS "${ZSHELL_CLI_WHEEL_STAMP}")
configure_file(
"${CMAKE_SOURCE_DIR}/Plugins/cmake/zshell-cli-python.cmake.in"
"${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli-python.cmake"
@ONLY
)
install(SCRIPT "${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli-python.cmake")
endif()
endif()
if("plugin" IN_LIST ENABLE_MODULES)
add_subdirectory(Plugins)
endif()
if("shell" IN_LIST ENABLE_MODULES)
@@ -71,3 +168,22 @@ if("shell" IN_LIST ENABLE_MODULES)
# Greeter
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
endif()
if("m3shapes" IN_LIST ENABLE_MODULES)
message(STATUS "Fetching M3Shapes module")
include(FetchContent)
set(M3SHAPES_REV bdc327b29f95394a732baf3c9b19658ba23755b6)
FetchContent_Declare(
m3shapes_external
GIT_REPOSITORY https://github.com/soramanew/m3shapes.git
GIT_TAG ${M3SHAPES_REV}
SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/m3shapes-${M3SHAPES_REV}"
)
FetchContent_MakeAvailable(m3shapes_external)
message(STATUS "Done fetching M3Shapes module")
# Fix m3shapes wrong rpath
if(TARGET m3shapesplugin)
set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN")
endif()
endif()
-135
View File
@@ -1,135 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ColumnLayout {
id: root
default property alias content: contentColumn.data
property string description: ""
property bool expanded: false
property bool nested: false
property bool showBackground: false
required property string title
signal toggleRequested
Layout.fillWidth: true
spacing: Appearance.spacing.small
Item {
id: sectionHeaderItem
Layout.fillWidth: true
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
RowLayout {
id: titleRow
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
font.pointSize: Appearance.font.size.larger
font.weight: 500
text: root.title
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
rotation: root.expanded ? 180 : 0
text: "expand_more"
Behavior on rotation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}
StateLayer {
anchors.fill: parent
color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal
showHoverBackground: false
onClicked: {
root.toggleRequested();
root.expanded = !root.expanded;
}
}
}
Item {
id: contentWrapper
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
clip: true
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomRect {
id: backgroundRect
anchors.fill: parent
color: DynamicColors.transparency.enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3surfaceContainer)
opacity: root.showBackground && root.expanded ? 1.0 : 0.0
radius: Appearance.rounding.normal
visible: root.showBackground
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
ColumnLayout {
id: contentColumn
anchors.bottomMargin: Appearance.spacing.small
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
opacity: root.expanded ? 1.0 : 0.0
spacing: Appearance.spacing.small
y: Appearance.spacing.small
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomText {
id: descriptionText
Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
Layout.fillWidth: true
Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: root.description
visible: root.description !== ""
wrapMode: Text.Wrap
}
}
}
}
-70
View File
@@ -1,70 +0,0 @@
import QtQuick
import QtQuick.Templates
import qs.Config
Slider {
id: root
property color nonPeakColor: DynamicColors.tPalette.m3primary
required property real peak
property color peakColor: DynamicColors.palette.m3primary
background: Item {
CustomRect {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 3
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 3
bottomRightRadius: root.implicitHeight / 15
color: root.nonPeakColor
implicitWidth: root.handle.x - root.implicitHeight
radius: Appearance.rounding.full
topRightRadius: root.implicitHeight / 15
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
implicitWidth: parent.width * root.peak
radius: Appearance.rounding.full
topRightRadius: root.implicitHeight / 15
Behavior on implicitWidth {
Anim {
duration: 50
}
}
}
}
CustomRect {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 3
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 3
bottomLeftRadius: root.implicitHeight / 15
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
radius: Appearance.rounding.full
topLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: Appearance.rounding.full
x: root.visualPosition * root.availableWidth - implicitWidth / 2
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
}
-69
View File
@@ -1,69 +0,0 @@
import QtQuick
import QtQuick.Controls.Basic
BusyIndicator {
id: control
property int busySize: 64
property color color: delegate.color
contentItem: Item {
implicitHeight: control.busySize
implicitWidth: control.busySize
Item {
id: item
height: control.busySize
opacity: control.running ? 1 : 0
width: control.busySize
x: parent.width / 2 - (control.busySize / 2)
y: parent.height / 2 - (control.busySize / 2)
Behavior on opacity {
OpacityAnimator {
duration: 250
}
}
RotationAnimator {
duration: 1250
from: 0
loops: Animation.Infinite
running: control.visible && control.running
target: item
to: 360
}
Repeater {
id: repeater
model: 6
CustomRect {
id: delegate
required property int index
color: control.color
implicitHeight: 10
implicitWidth: 10
radius: 5
x: item.width / 2 - width / 2
y: item.height / 2 - height / 2
transform: [
Translate {
y: -Math.min(item.width, item.height) * 0.5 + 5
},
Rotation {
angle: delegate.index / repeater.count * 360
origin.x: 5
origin.y: 5
}
]
}
}
}
}
}
-32
View File
@@ -1,32 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Config
Button {
id: control
property color bgColor: DynamicColors.palette.m3primary
property int radius: Appearance.rounding.smallest / 2
property color textColor: DynamicColors.palette.m3onPrimary
background: CustomRect {
color: control.bgColor
opacity: control.enabled ? 1.0 : 0.5
radius: control.radius
}
contentItem: CustomText {
color: control.textColor
horizontalAlignment: Text.AlignHCenter
opacity: control.enabled ? 1.0 : 0.5
text: control.text
verticalAlignment: Text.AlignVCenter
}
StateLayer {
radius: control.radius
onClicked: {
control.clicked();
}
}
}
-169
View File
@@ -1,169 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Config
ComboBox {
id: root
property int cornerRadius: Appearance.rounding.normal
property int fieldHeight: 42
property bool filled: true
property real focusRingOpacity: 0.70
property int hPadding: 16
property int menuCornerRadius: 16
property int menuRowHeight: 46
property int menuVisibleRows: 7
property bool preferPopupWindow: false
hoverEnabled: true
implicitHeight: fieldHeight
implicitWidth: 240
spacing: 8
// ---------- Field background (filled/outlined + state layers + focus ring) ----------
background: Item {
anchors.fill: parent
CustomRect {
id: container
anchors.fill: parent
color: DynamicColors.palette.m3surfaceVariant
radius: root.cornerRadius
StateLayer {
}
}
}
// ---------- Content ----------
contentItem: RowLayout {
anchors.fill: parent
anchors.leftMargin: root.hPadding
anchors.rightMargin: root.hPadding
spacing: 12
// Display text
CustomText {
Layout.fillWidth: true
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pixelSize: 16
font.weight: Font.Medium
text: root.currentText
verticalAlignment: Text.AlignVCenter
}
// Indicator chevron (simple, replace with your icon system)
CustomText {
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurfaceVariant
rotation: root.popup.visible ? 180 : 0
text: "▾"
transformOrigin: Item.Center
verticalAlignment: Text.AlignVCenter
Behavior on rotation {
NumberAnimation {
duration: 140
easing.type: Easing.OutCubic
}
}
}
}
popup: Popup {
id: p
implicitHeight: list.contentItem.height + Appearance.padding.small * 2
implicitWidth: root.width
modal: true
popupType: root.preferPopupWindow ? Popup.Window : Popup.Item
y: -list.currentIndex * (root.menuRowHeight + Appearance.spacing.small) - Appearance.padding.small
background: CustomRect {
color: DynamicColors.palette.m3surface
radius: root.menuCornerRadius
}
contentItem: ListView {
id: list
anchors.bottomMargin: Appearance.padding.small
anchors.fill: parent
anchors.topMargin: Appearance.padding.small
clip: true
currentIndex: root.currentIndex
model: root.delegateModel
spacing: Appearance.spacing.small
delegate: CustomRect {
required property int index
required property var modelData
anchors.horizontalCenter: parent.horizontalCenter
color: (index === root.currentIndex) ? DynamicColors.palette.m3primary : "transparent"
implicitHeight: root.menuRowHeight
implicitWidth: p.implicitWidth - Appearance.padding.small * 2
radius: Appearance.rounding.normal - Appearance.padding.small
RowLayout {
anchors.fill: parent
spacing: 10
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pixelSize: 15
text: modelData
verticalAlignment: Text.AlignVCenter
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: "✓"
verticalAlignment: Text.AlignVCenter
visible: index === root.currentIndex
}
}
StateLayer {
onClicked: {
root.currentIndex = index;
p.close();
}
}
}
}
// Expressive-ish open/close motion: subtle scale+fade (tune to taste). :contentReference[oaicite:5]{index=5}
enter: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
Anim {
from: 0.98
property: "scale"
to: 1.0
}
}
exit: Transition {
Anim {
from: 1
property: "opacity"
to: 0
}
}
Elevation {
anchors.fill: parent
level: 2
radius: root.menuCornerRadius
z: -1
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ ListView {
property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing
interactive: !Visibilities.getForActive()?.isDrawing
maximumFlickVelocity: 3000
rebound: Transition {
-25
View File
@@ -1,25 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Components
ToolTip {
id: root
property bool alternativeVisibleCondition: false
property bool extraVisibleCondition: true
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
background: null
horizontalPadding: 10
verticalPadding: 5
visible: internalVisibleCondition
contentItem: CustomTooltipContent {
id: contentItem
horizontalPadding: root.horizontalPadding
shown: root.internalVisibleCondition
text: root.text
verticalPadding: root.verticalPadding
}
}
-54
View File
@@ -1,54 +0,0 @@
import QtQuick
import qs.Components
import qs.Config
Item {
id: root
property real horizontalPadding: 10
property bool isVisible: backgroundRectangle.implicitHeight > 0
property bool shown: false
required property string text
property real verticalPadding: 5
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
Rectangle {
id: backgroundRectangle
clip: true
color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043"
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
opacity: shown ? 1 : 0
radius: Appearance.rounding.smallest
Behavior on implicitHeight {
Anim {
}
}
Behavior on implicitWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
CustomText {
id: tooltipTextObject
anchors.centerIn: parent
color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF"
text: root.text
wrapMode: Text.Wrap
}
}
}
-44
View File
@@ -1,44 +0,0 @@
import qs.Config
import QtQuick
CustomRect {
required property int extra
anchors.margins: 8
anchors.right: parent.right
color: DynamicColors.palette.m3tertiary
implicitHeight: count.implicitHeight + 4 * 2
implicitWidth: count.implicitWidth + 8 * 2
opacity: extra > 0 ? 1 : 0
radius: Appearance.rounding.smallest
scale: extra > 0 ? 1 : 0.5
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
Behavior on scale {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Elevation {
anchors.fill: parent
level: 2
opacity: parent.opacity
radius: parent.radius
z: -1
}
CustomText {
id: count
anchors.centerIn: parent
animate: parent.opacity > 0
color: DynamicColors.palette.m3onTertiary
text: qsTr("+%1").arg(parent.extra)
}
}
-33
View File
@@ -1,33 +0,0 @@
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;
}
}
}
+114
View File
@@ -0,0 +1,114 @@
import QtQuick
import Quickshell
import M3Shapes
import qs.Config
MaterialShape {
id: root
property bool animated: true
property real cRotation
property bool containsIcon
property real dampingRatio: 0.6
property real lRotation
property int morphAnimRotation: 60
property real morphScale: 0.14
property alias rotateAnimDuration: rotateAnim.duration
property int shapeIndex
property list<int> shapes: {
if (containsIcon)
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pentagon, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
}
readonly property real springDuration: {
const wn = Math.sqrt(stiffness);
const r = -dampingRatio * wn;
const c = 1 / Math.sqrt(1 - dampingRatio * dampingRatio);
return Math.log(visibilityThreshold / c) / r;
}
readonly property real springMaxVelocity: {
const wn = Math.sqrt(stiffness);
const factor = Math.exp(-z * Math.acos(z) / Math.sqrt(1 - z * z));
return wn * factor;
}
property bool springSettled: true
property real stiffness: 180
property real thisLRotation
property real visibilityThreshold: 0.075
function spring(t: real): var {
const wn = Math.sqrt(stiffness);
const za = dampingRatio * wn;
const wd = wn * Math.sqrt(1 - dampingRatio * dampingRatio);
const r = za / wd;
const pos = 1 - Math.exp(-za * t) * (Math.cos(wd * t) + r * Math.sin(wd * t));
const vel = Math.exp(-za * t) * (wn * wn / wd) * Math.sin(wd * t);
return [pos, vel];
}
color: DynamicColors.palette.m3primary
implicitSize: 38
toShape: shapes[0]
RotationAnimation on cRotation {
id: rotateAnim
duration: 4666
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
running: root.animated
to: 360
}
Behavior on color {
CAnim {
}
}
ElapsedTimer {
id: timer
}
FrameAnimation {
running: root.animated && !root.springSettled
onTriggered: {
const t = timer.elapsed();
if (t >= root.springDuration) {
root.springSettled = true;
} else {
const [pos, vel] = root.spring(t);
root.morphProgress = Math.min(1, pos); // Overshooting the morph looks weird
root.thisLRotation = pos * root.morphAnimRotation;
root.scale = 1 + vel * root.morphScale / root.springMaxVelocity;
}
}
}
Timer {
interval: 650
repeat: true
running: root.animated
triggeredOnStart: true
onTriggered: {
root.beginBatchUpdate();
root.fromShape = root.toShape;
root.shapeIndex = (root.shapeIndex + 1) % root.shapes.length;
root.toShape = root.shapes[root.shapeIndex];
root.morphProgress = 0;
root.rotation = root.rotation;
root.lRotation = (root.lRotation + root.thisLRotation) % 360;
root.thisLRotation = 0;
root.rotation = Qt.binding(() => root.cRotation + root.lRotation + root.thisLRotation);
root.springSettled = false;
timer.restart();
root.endBatchUpdate();
}
}
}
-309
View File
@@ -1,309 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Config
Item {
id: root
enum Variant {
Outlined,
Filled
}
readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline))
readonly property real disabledOpacity: 0.38
readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : ""))
property string errorText: ""
readonly property alias field: field
readonly property bool floating: focused || hasContent || prefixText.length > 0
readonly property bool focused: field.activeFocus
readonly property bool hasContent: field.text.length > 0
readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property int inputMethodHints: Qt.ImhNone
property bool isError: false
property string label: ""
readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property string leadingIcon: ""
property int maxLength: 0
property color notchColor: DynamicColors.palette.m3surface
readonly property bool outlined: variant === M3TextField.Variant.Outlined
property bool password: false
property string placeholder: ""
property string prefixText: ""
property string suffixText: ""
readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant)
property string supportingText: ""
property alias text: field.text
property string trailingIcon: ""
readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0
property var validator: null
property int variant: M3TextField.Variant.Outlined
signal accepted
function forceFieldFocus(): void {
field.forceActiveFocus();
}
implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0)
implicitWidth: 240
CustomRect {
id: filledBg
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface
implicitHeight: 56
opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4%
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
visible: !root.outlined
Behavior on color {
CAnim {
}
}
StateLayer {
anchors.fill: parent
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: root.accent
implicitHeight: root.focused || root.isError ? 2 : 1
opacity: root.enabled ? 1 : root.disabledOpacity
Behavior on color {
CAnim {
}
}
Behavior on implicitHeight {
Anim {
type: Anim.StandardSmall
}
}
}
}
CustomRect {
id: outline
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
border.color: root.accent
border.width: root.focused || root.isError ? 2 : 1
color: "transparent"
implicitHeight: 56
opacity: root.enabled ? 1 : root.disabledOpacity
radius: Appearance.rounding.small
visible: root.outlined
Behavior on border.color {
CAnim {
}
}
Behavior on border.width {
Anim {
type: Anim.StandardSmall
}
}
StateLayer {
anchors.fill: parent
anchors.margins: outline.border.width
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
radius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
}
Rectangle {
color: root.notchColor
height: outline.border.width
opacity: root.enabled ? 1 : root.disabledOpacity
visible: root.outlined && root.floating && root.label.length > 0
width: labelText.width + Appearance.spacing.extraSmall
x: labelText.x - Appearance.spacing.extraSmall / 2
y: 0
}
CustomText {
id: labelText
color: root.labelColor
font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.label
visible: root.label.length > 0
x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger)
y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2
Behavior on color {
CAnim {
}
}
Behavior on x {
Anim {
type: Anim.StandardSmall
}
}
Behavior on y {
Anim {
type: Anim.StandardSmall
}
}
}
MaterialIcon {
id: leading
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.iconColor
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.leadingIcon
visible: root.leadingIcon.length > 0
Behavior on color {
CAnim {
}
}
}
MaterialIcon {
id: trailing
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface)
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.effectiveTrailingIcon
visible: root.effectiveTrailingIcon.length > 0
MouseArea {
anchors.fill: parent
anchors.margins: -Appearance.padding.small
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: root.trailingInteractive && root.enabled
onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password
}
}
CustomText {
id: prefix
anchors.left: leading.visible ? leading.right : parent.left
anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.prefixText
verticalAlignment: Text.AlignVCenter
visible: root.prefixText.length > 0 && root.floating
}
CustomText {
id: suffix
anchors.right: trailing.visible ? trailing.left : parent.right
anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.suffixText
verticalAlignment: Text.AlignVCenter
visible: root.suffixText.length > 0 && root.floating
}
CustomTextField {
id: field
anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left)
anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right)
anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.top: parent.top
color: DynamicColors.palette.m3onSurface
echoMode: root.password ? TextInput.Password : TextInput.Normal
enabled: root.enabled
font.pointSize: Appearance.font.size.medium
height: 56
inputMethodHints: root.inputMethodHints
maximumLength: root.maxLength > 0 ? root.maxLength : 32767
opacity: root.enabled ? 1 : root.disabledOpacity
placeholderText: root.focused ? root.placeholder : ""
topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
validator: root.validator
verticalAlignment: TextInput.AlignVCenter
onAccepted: root.accepted()
onTextEdited: if (root.isError)
root.isError = false
}
Item {
id: supportRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 56 + Appearance.spacing.extraSmall
implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight)
visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0
CustomText {
id: supportText
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.right: counter.visible ? counter.left : parent.right
anchors.rightMargin: Appearance.spacing.small
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText
wrapMode: Text.WordWrap
}
CustomText {
id: counter
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: `${root.text.length}/${root.maxLength}`
visible: root.maxLength > 0
}
}
}
-76
View File
@@ -1,76 +0,0 @@
import QtQuick
Path {
id: root
required property real viewHeight
required property real viewWidth
startX: root.viewWidth / 2
startY: 0
PathAttribute {
name: "itemOpacity"
value: 0.25
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (1 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (2 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (3 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 1.00
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (4 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (5 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight
}
PathAttribute {
name: "itemOpacity"
value: 0.25
}
}
-180
View File
@@ -1,180 +0,0 @@
import QtQuick
import QtQuick.Effects
import qs.Config
Elevation {
id: root
required property int currentIndex
property bool expanded
required property int from
property color insideTextColor: DynamicColors.palette.m3onPrimary
property int itemHeight
property int listHeight: 200
property color outsideTextColor: DynamicColors.palette.m3onSurfaceVariant
readonly property var spinnerModel: root.range(root.from, root.to)
required property int to
property Item triggerItem
signal itemSelected(item: int)
function range(first, last) {
let out = [];
for (let i = first; i <= last; ++i)
out.push(i);
return out;
}
implicitHeight: root.expanded ? view.implicitHeight : 0
level: root.expanded ? 2 : 0
radius: itemHeight / 2
visible: implicitHeight > 0
z: root.expanded ? 100 : 0
Behavior on implicitHeight {
Anim {
}
}
onExpandedChanged: {
if (!root.expanded)
root.itemSelected(view.currentIndex + 1);
}
Component {
id: spinnerDelegate
Item {
id: wrapper
readonly property color delegateTextColor: wrapper.PathView.view ? wrapper.PathView.view.delegateTextColor : "white"
required property var modelData
height: root.itemHeight
opacity: wrapper.PathView.itemOpacity
visible: wrapper.PathView.onPath
width: wrapper.PathView.view ? wrapper.PathView.view.width : 0
z: wrapper.PathView.isCurrentItem ? 100 : Math.round(wrapper.PathView.itemScale * 100)
CustomText {
anchors.centerIn: parent
color: wrapper.delegateTextColor
font.pointSize: Appearance.font.size.large
text: wrapper.modelData
}
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
z: root.z
// Main visible spinner: normal/outside text color
PathView {
id: view
property color delegateTextColor: root.outsideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: root.currentIndex - 1
delegate: spinnerDelegate
dragMargin: width
highlightRangeMode: PathView.StrictlyEnforceRange
implicitHeight: root.listHeight
model: root.spinnerModel
pathItemCount: 7
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
snapMode: PathView.SnapToItem
path: PathMenu {
viewHeight: view.height
viewWidth: view.width
}
}
// The selection rectangle itself
CustomRect {
id: selectionRect
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
height: root.itemHeight
radius: root.itemHeight / 2
width: parent.width
z: 2
}
// Hidden source: same PathView, but with the "inside selection" text color
Item {
id: selectedTextSource
anchors.fill: parent
layer.enabled: true
visible: false
PathView {
id: selectedTextView
property color delegateTextColor: root.insideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: view.currentIndex
delegate: spinnerDelegate
dragMargin: view.dragMargin
highlightRangeMode: view.highlightRangeMode
implicitHeight: root.listHeight
interactive: false
model: view.model
// Keep this PathView visually locked to the real one
offset: view.offset
pathItemCount: view.pathItemCount
preferredHighlightBegin: view.preferredHighlightBegin
preferredHighlightEnd: view.preferredHighlightEnd
snapMode: view.snapMode
path: PathMenu {
viewHeight: selectedTextView.height
viewWidth: selectedTextView.width
}
}
}
// Mask matching the selection rectangle
Item {
id: selectionMask
anchors.fill: parent
layer.enabled: true
visible: false
CustomRect {
color: "white"
height: selectionRect.height
radius: selectionRect.radius
width: selectionRect.width
x: selectionRect.x
y: selectionRect.y
}
}
// Only show the "inside selection" text where the mask exists
MultiEffect {
anchors.fill: selectedTextSource
maskEnabled: true
maskInverted: false
maskSource: selectionMask
source: selectedTextSource
z: 3
}
}
}
-50
View File
@@ -1,50 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
CustomRect {
id: root
required property string label
required property real max
required property real min
property var onValueModified: function (value) {}
property real step: 1
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
implicitHeight: row.implicitHeight + Appearance.padding.large * 2
radius: Appearance.rounding.normal
Behavior on implicitHeight {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
Layout.fillWidth: true
text: root.label
}
CustomSpinBox {
max: root.max
min: root.min
step: root.step
value: root.value
onValueModified: value => {
root.onValueModified(value);
}
}
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ MouseArea {
anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !Visibilities.getForActive().isDrawing
enabled: parent.enabled && !Visibilities.getForActive()?.isDrawing
hoverEnabled: true
Behavior on stateOpacity {
+48 -18
View File
@@ -6,25 +6,55 @@ import qs.Effects
CustomListView {
id: root
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real endFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
readonly property bool horizontal: orientation === ListView.Horizontal
property real startFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
return false;
if (isStart)
return visibleArea.yPosition > 0;
return visibleArea.yPosition + visibleArea.heightRatio < 1;
function contentSize(): real {
return horizontal ? contentWidth : contentHeight;
}
flickableDirection: Flickable.VerticalFlick
layer.enabled: true
orientation: ListView.Vertical
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts.
if (contentSize() + marginStart() + marginEnd() < viewportSize() && rebound.running && ((isStart ? overshootStart() > 0 : overshootStart() < 0))) {
return false;
}
Behavior on bottomFadeOpacity {
if (isStart)
return visibleStart() > 0;
return visibleStart() + visibleRatio() < 1;
}
function marginEnd(): real {
return horizontal ? rightMargin : bottomMargin;
}
function marginStart(): real {
return horizontal ? leftMargin : topMargin;
}
function overshootStart(): real {
return horizontal ? horizontalOvershoot : verticalOvershoot;
}
function viewportSize(): real {
return horizontal ? width : height;
}
function visibleRatio(): real {
return horizontal ? visibleArea.widthRatio : visibleArea.heightRatio;
}
function visibleStart(): real {
return horizontal ? visibleArea.xPosition : visibleArea.yPosition;
}
flickableDirection: horizontal ? Flickable.HorizontalFlick : Flickable.VerticalFlick
layer.enabled: true
Behavior on endFadeOpacity {
Anim {
type: Anim.SlowEffects
}
@@ -40,10 +70,10 @@ CustomListView {
visible: false
gradient: Gradient {
orientation: Gradient.Vertical
orientation: root.horizontal ? Gradient.Horizontal : Gradient.Vertical
GradientStop {
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
color: Qt.rgba(0, 0, 0, root.startFadeOpacity)
position: 0
}
@@ -58,13 +88,13 @@ CustomListView {
}
GradientStop {
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
color: Qt.rgba(0, 0, 0, root.endFadeOpacity)
position: 1
}
}
}
}
Behavior on topFadeOpacity {
Behavior on startFadeOpacity {
Anim {
type: Anim.SlowEffects
}
+46
View File
@@ -0,0 +1,46 @@
import QtQuick
import QtQuick.Shapes
import qs.Config
Shape {
id: root
property real amplitude: 3
property color color: DynamicColors.palette.m3surfaceContainer
readonly property real waveHeight: amplitude * 2
property int waves: 4
asynchronous: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: root.color
strokeColor: "transparent"
strokeWidth: 0
Behavior on fillColor {
CAnim {
}
}
PathSvg {
path: {
const w = root.width;
const h = root.height;
const a = root.amplitude;
const n = Math.max(1, root.waves);
const wl = w / n;
const half = wl / 2;
let d = `M 0,${a} `;
for (let i = 0; i < n; ++i) {
const x = i * wl;
d += `Q ${x + half / 2},${-a} ${x + half},${a} `;
d += `Q ${x + half + half / 2},${3 * a} ${x + wl},${a} `;
}
d += `L ${w},${h} L 0,${h} Z`;
return d;
}
}
}
}
+2
View File
@@ -72,10 +72,12 @@ JsonObject {
property bool upower: true
}
component Tray: JsonObject {
property bool recolorIcons: false
property bool showAudio: true
property bool showBluetooth: false
property bool showMicrophone: true
property bool showNetwork: false
property bool showOnHover: false
property bool showPower: true
property bool showWifi: false
property int trayIconSize: 24
+1
View File
@@ -17,6 +17,7 @@ JsonObject {
property bool showMemory: true
property bool showNetwork: true
property bool showStorage: true
property bool showVram: true
}
component Sizes: JsonObject {
readonly property int dateTimeWidth: 110
+3 -14
View File
@@ -170,7 +170,7 @@ Item {
root.panels.osd.hovered = false;
}
if (!root.popouts.currentName.startsWith("traymenu")) {
if (!root.popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
root.popouts.hasCurrent = false;
}
@@ -196,7 +196,7 @@ Item {
if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight)
root.bar.isHovered = true;
if (root.panels.sidebar.width === 0) {
if (root.panels.sidebar.offsetScale === 1) {
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
if (showOsd) {
@@ -204,7 +204,7 @@ Item {
root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < root.width - root.panels.sidebar.width;
const outOfSidebar = x < root.width - root.panels.sidebar.width * (1 - root.panels.sidebar.offsetScale);
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
if (!root.osdShortcutActive) {
@@ -311,17 +311,6 @@ Item {
}
}
function onUtilitiesChanged() {
if (root.visibilities.utilities) {
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true;
}
} else {
root.utilitiesShortcutActive = false;
}
}
target: root.visibilities
}
}
+3 -7
View File
@@ -184,18 +184,14 @@ Item {
Item {
id: settingsWrapper
anchors.fill: parent
clip: true
implicitHeight: settings.implicitHeight
implicitWidth: settings.implicitWidth
x: (root.width - settings.implicitWidth) / 2
y: (settings.implicitHeight + (root.height - root.bar.implicitHeight - settings.implicitHeight) / 2) * (1 - settings.offsetScale) - settings.implicitHeight - 5
Settings.Wrapper {
id: settings
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
// anchors.centerIn: parent
anchors.centerIn: parent
anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
panels: root
screen: root.screen
visibilities: root.visibilities
+1 -2
View File
@@ -38,7 +38,6 @@ Region {
R {
panel: root.panels.osdWrapper
width: panel.width * (1 - root.panels.osd.offsetScale) + root.borderThickness
x: root.win.width - width
}
R {
@@ -60,7 +59,7 @@ Region {
}
R {
panel: root.panels.settingsWrapper
panel: root.panels.settings
}
R {
+5 -18
View File
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Io
@@ -25,7 +26,7 @@ CustomWindow {
if (focusGrab.active)
return 0;
if (monitor?.lastIpcObject.specialWorkspace?.name || monitor?.activeWorkspace.lastIpcObject.windows > 0)
if (monitor?.lastIpcObject.specialWorkspace?.name || monitor?.activeWorkspace?.lastIpcObject.windows > 0)
return 0;
return 100;
@@ -127,7 +128,7 @@ CustomWindow {
HyprlandFocusGrab {
id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
windows: [root]
onCleared: {
@@ -161,14 +162,6 @@ CustomWindow {
Component.onCompleted: Visibilities.load(root.screen, this)
}
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding {
property: "bar"
target: visibilities
@@ -305,15 +298,9 @@ CustomWindow {
PanelBg {
id: settingsBg
property real extraHeight: 0
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settingsWrapper
panel: panels.settings
radius: Appearance.rounding.large + Appearance.padding.normal
x: panels.settingsWrapper.x + panels.settings.x + root.borderThickness
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
@@ -421,7 +408,7 @@ CustomWindow {
resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix
}
settingsWrapper.transform: Matrix4x4 {
settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
-12
View File
@@ -30,18 +30,6 @@ RowLayout {
}
}
// CustomRect {
// Layout.fillWidth: true
// color: DynamicColors.tPalette.m3surfaceContainer
// implicitHeight: resources.implicitHeight
// radius: Appearance.rounding.small
//
// Resources {
// id: resources
//
// }
// }
CustomClippingRect {
Layout.fillHeight: true
Layout.fillWidth: true
-17
View File
@@ -1,17 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
icon = icon.replace("qspixmap", "icon/discord-tray");
}
return icon;
}
}
-60
View File
@@ -1,60 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property Item activeMenu: null
property Item activeTrigger: null
function close(menu) {
if (!menu)
return;
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
menu.expanded = false;
}
function closeActive() {
if (activeMenu)
activeMenu.expanded = false;
activeMenu = null;
activeTrigger = null;
}
function forget(menu) {
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
}
function hit(item, scenePos) {
if (!item || !item.visible)
return false;
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
return item.contains(p);
}
function open(menu, trigger) {
if (activeMenu && activeMenu !== menu)
activeMenu.expanded = false;
activeMenu = menu;
activeTrigger = trigger || null;
menu.expanded = true;
}
function toggle(menu, trigger) {
if (activeMenu === menu && menu.expanded)
close(menu);
else
open(menu, trigger);
}
}
-409
View File
@@ -1,409 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
import qs.Config
Singleton {
id: root
property string autoGpuType: "NONE"
property string cpuName: ""
property real cpuPerc
property real cpuTemp
// Individual disks: Array of { mount, used, total, free, perc }
property var disks: []
property real gpuMemTotal: 0
property real gpuMemUsed
property real gpuPerc
property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property real lastCpuIdle
property real lastCpuTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
property real memTotal
property real memUsed
property int refCount
readonly property real storagePerc: {
let totalUsed = 0;
let totalSize = 0;
for (const disk of disks) {
totalUsed += disk.used;
totalSize += disk.total;
}
return totalSize > 0 ? totalUsed / totalSize : 0;
}
function cleanCpuName(name: string): string {
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
}
function cleanGpuName(name: string): string {
return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim();
}
function formatKib(kib: real): var {
const mib = 1024;
const gib = 1024 ** 2;
const tib = 1024 ** 3;
if (kib >= tib)
return {
value: kib / tib,
unit: "TiB"
};
if (kib >= gib)
return {
value: kib / gib,
unit: "GiB"
};
if (kib >= mib)
return {
value: kib / mib,
unit: "MiB"
};
return {
value: kib,
unit: "KiB"
};
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
stat.reload();
meminfo.reload();
if (root.gpuType === "GENERIC")
gpuUsage.running = true;
}
}
Timer {
interval: 60000 * 120
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
storage.running = true;
}
}
Timer {
interval: Config.dashboard.resourceUpdateInterval * 5
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
sensors.running = true;
}
}
FileView {
id: cpuinfoInit
path: "/proc/cpuinfo"
onLoaded: {
const nameMatch = text().match(/model name\s*:\s*(.+)/);
if (nameMatch)
root.cpuName = root.cleanCpuName(nameMatch[1]);
}
}
FileView {
id: stat
path: "/proc/stat"
onLoaded: {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle;
const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
root.lastCpuTotal = total;
root.lastCpuIdle = idle;
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
root.cpuPerc = newCpuPerc;
}
}
}
FileView {
id: meminfo
path: "/proc/meminfo"
onLoaded: {
const data = text();
const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
if (root.memTotal !== total)
root.memTotal = total;
if (Math.abs(used - root.memUsed) >= 16384)
root.memUsed = used;
}
}
Process {
id: storage
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"]
stdout: StdioCollector {
onStreamFinished: {
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
const lines = text.trim().split("\n");
for (const line of lines) {
if (line.trim() === "")
continue;
const nameMatch = line.match(/NAME="([^"]+)"/);
const sizeMatch = line.match(/SIZE="([^"]+)"/);
const typeMatch = line.match(/TYPE="([^"]+)"/);
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
if (!nameMatch || !typeMatch)
continue;
const name = nameMatch[1];
const type = typeMatch[1];
const size = parseInt(sizeMatch?.[1] || "0", 10);
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
if (type === "disk") {
// Skip zram (swap) devices
if (name.startsWith("zram"))
continue;
// Initialize disk entry
if (!diskMap[name]) {
diskMap[name] = {
name: name,
totalSize: size,
used: 0,
fsTotal: 0
};
}
} else if (type === "part") {
// Find parent disk (remove trailing numbers/p+numbers)
let parentDisk = name.replace(/p?\d+$/, "");
// For nvme devices like nvme0n1p1, parent is nvme0n1
if (name.match(/nvme\d+n\d+p\d+/))
parentDisk = name.replace(/p\d+$/, "");
// Aggregate partition usage to parent disk
if (diskMap[parentDisk]) {
diskMap[parentDisk].used += fsused;
diskMap[parentDisk].fsTotal += fssize;
}
}
}
const diskList = [];
let totalUsed = 0;
let totalSize = 0;
for (const diskName of Object.keys(diskMap).sort()) {
const disk = diskMap[diskName];
// Use filesystem total if available, otherwise use disk size
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
const used = disk.used;
const perc = total > 0 ? used / total : 0;
// Convert bytes to KiB for consistency with formatKib
diskList.push({
mount: disk.name // Using 'mount' property for compatibility
,
used: used / 1024,
total: total / 1024,
free: (total - used) / 1024,
perc: perc
});
totalUsed += used;
totalSize += total;
}
root.disks = diskList;
}
}
}
Process {
id: gpuNameDetect
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const output = text.trim();
if (!output)
return;
// Check if it's from nvidia-smi (clean GPU name)
if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
root.gpuName = root.cleanGpuName(output);
} else {
// Parse lspci output: extract name from brackets or after colon
const bracketMatch = output.match(/\[([^\]]+)\]/);
if (bracketMatch) {
root.gpuName = root.cleanGpuName(bracketMatch[1]);
} else {
const colonMatch = output.match(/:\s*(.+)/);
if (colonMatch)
root.gpuName = root.cleanGpuName(colonMatch[1]);
}
}
}
}
}
Process {
id: gpuTypeCheck
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
running: !Config.services.gpuType
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
root.gpuMemTotal = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: gpuUsageNvidia
command: ["/usr/bin/nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits", "-lms", "1000"]
running: root.refCount > 0 && root.gpuType === "NVIDIA"
stdout: SplitParser {
onRead: data => {
const parts = String(data).trim().split(/\s*,\s*/);
if (parts.length < 3)
return;
const usageRaw = parseInt(parts[0], 10);
const tempRaw = parseInt(parts[1], 10);
const memRaw = parseInt(parts[2], 10);
if (!Number.isFinite(usageRaw) || !Number.isFinite(tempRaw) || !Number.isFinite(memRaw))
return;
const newGpuPerc = Math.max(0, Math.min(1, usageRaw / 100));
const newGpuTemp = tempRaw;
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memRaw / root.gpuMemTotal)) : 0;
// Only publish meaningful changes to avoid needless binding churn / repaints
if (Math.abs(root.gpuPerc - newGpuPerc) >= 0.01)
root.gpuPerc = newGpuPerc;
if (Math.abs(root.gpuTemp - newGpuTemp) >= 1)
root.gpuTemp = newGpuTemp;
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
root.gpuMemUsed = newGpuMemUsed;
}
}
}
Process {
id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : ["echo"]
stdout: StdioCollector {
onStreamFinished: {
if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.gpuPerc = sum / percs.length / 100;
} else {
root.gpuPerc = 0;
root.gpuTemp = 0;
}
}
}
}
Process {
id: sensors
command: ["sensors"]
environment: ({
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8"
})
stdout: StdioCollector {
onStreamFinished: {
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
root.cpuTemp = parseFloat(cpuTemp[1]);
if (root.gpuType !== "GENERIC")
return;
let eligible = false;
let sum = 0;
let count = 0;
for (const line of text.trim().split("\n")) {
if (line === "Adapter: PCI adapter")
eligible = true;
else if (line === "")
eligible = false;
else if (eligible) {
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (match) {
sum += parseFloat(match[2]);
count++;
}
}
}
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
}
}
-6
View File
@@ -1,6 +0,0 @@
import QtQuick
Text {
renderType: Text.NativeRendering
textFormat: Text.PlainText
}
-288
View File
@@ -1,288 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
property list<DesktopEntry> entryList: []
property var preppedIcons: []
property var preppedIds: []
property var preppedNames: []
// Dynamic fixups
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property real scoreThreshold: 0.2
// Manual overrides for tricky apps
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot"
})
function checkCleanMatch(str) {
if (!str || str.length <= 3)
return null;
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
// Aggressive fallback: strip all separators
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
for (let i = 0; i < list.length; i++) {
const entry = list[i];
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
return entry;
}
}
return null;
}
function checkFuzzySearch(str) {
if (typeof FuzzySort === 'undefined')
return null;
// Check filenames (IDs) first
if (preppedIds.length > 0) {
let results = fuzzyQuery(str, preppedIds);
if (results.length === 0) {
const underscored = str.replace(/-/g, '_').toLowerCase();
if (underscored !== str)
results = fuzzyQuery(underscored, preppedIds);
}
if (results.length > 0)
return results[0];
}
// Then icons
if (preppedIcons.length > 0) {
const results = fuzzyQuery(str, preppedIcons);
if (results.length > 0)
return results[0];
}
// Then names
if (preppedNames.length > 0) {
const results = fuzzyQuery(str, preppedNames);
if (results.length > 0)
return results[0];
}
return null;
}
// --- Lookup Helpers ---
function checkHeuristic(str) {
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
const lower = str.toLowerCase();
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
for (let i = 0; i < variants.length; i++) {
const variant = variants[i];
if (variant) {
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
}
}
return null;
}
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function fuzzyQuery(search, preppedData) {
if (!search || !preppedData || preppedData.length === 0)
return [];
return FuzzySort.go(search, preppedData, {
all: true,
key: "name"
}).map(r => r.obj.entry);
}
function getFromReverseDomain(str) {
if (!str)
return "";
return str.split('.').slice(-1)[0];
}
// Deprecated shim
function guessIcon(str) {
const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
}
function iconExists(iconName) {
if (!iconName || iconName.length === 0)
return false;
if (iconName.startsWith("/"))
return true;
const path = Quickshell.iconPath(iconName, true);
return path && path.length > 0 && !path.includes("image-missing");
}
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable";
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return "";
}
}
function normalizeWithHyphens(str) {
if (!str)
return "";
return str.toLowerCase().replace(/\s+/g, "-");
}
function refreshEntries() {
if (typeof DesktopEntries === 'undefined')
return;
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `),
entry: a
}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
Component.onCompleted: refreshEntries()
Connections {
function onValuesChanged() {
refreshEntries();
}
target: DesktopEntries.applications
}
}
-77
View File
@@ -1,77 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Helpers
import qs.Config
GridLayout {
id: root
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
columnSpacing: Appearance.spacing.large
columns: 2
rowSpacing: Appearance.spacing.large
rows: 1
Ref {
service: SystemUsage
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3primary
icon: "memory"
value: SystemUsage.cpuPerc
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: SystemUsage.memPerc
}
component Resource: CustomRect {
id: res
required property color colour
required property string icon
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: width
radius: Appearance.rounding.large
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
CircularProgress {
id: circ
anchors.fill: parent
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
fgColour: res.colour
padding: Appearance.padding.large * 3
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
value: res.value
}
MaterialIcon {
id: icon
anchors.centerIn: parent
color: res.colour
font.pointSize: (circ.arcRadius * 0.7) || 1
font.weight: 600
text: res.icon
}
}
}
+1 -1
View File
@@ -9,5 +9,5 @@ export EGL_PLATFORM=gbm
if command -v start-hyprland >/dev/null 2>&1; then
exec start-hyprland -- -c /etc/zshell-greeter/zshell-hyprland.conf
else
exec Hyprland -c /etc/zshell-greeter/zshell-hyprland.conf
exec Hyprland -c /etc/zshell-greeter/zshell-hyprland.lua
fi
-12
View File
@@ -1,12 +0,0 @@
monitor = ,preferred,auto,1
env = XDG_SESSION_TYPE,wayland
env = QT_QPA_PLATFORM,wayland
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
}
exec = sh -lc 'qs -c zshell-greeter; hyprctl dispatch exit'
+14
View File
@@ -0,0 +1,14 @@
hl.env("XDG_SESSION_TYPE", "wayland")
hl.env("QT_QPA_PLATFORM", "wayland")
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
hl.config({
misc = {
disable_hyprland_logo = true,
disable_splash_rendering = true,
},
})
hl.on("hyprland.start", function()
hl.exec_cmd("sh -lc 'qs -c zshell-greeter; hyprctl dispatch exit'")
end)
+15 -42
View File
@@ -11,8 +11,13 @@ Singleton {
id: root
property bool appleDisplayPresent: false
readonly property var ddcMonitorMap: {
const map = {};
for (const m of ddcMonitors)
map[m.connector] = m;
return map;
}
property list<var> ddcMonitors: []
property list<var> ddcServiceMon: []
readonly property list<Monitor> monitors: variants.instances
function decreaseBrightness(): void {
@@ -56,8 +61,6 @@ Singleton {
onMonitorsChanged: {
ddcMonitors = [];
ddcServiceMon = [];
ddcServiceProc.running = true;
ddcProc.running = true;
}
@@ -92,26 +95,6 @@ Singleton {
}
}
Process {
id: ddcServiceProc
command: ["ddcutil-client", "detect"]
// running: true
stdout: StdioCollector {
onStreamFinished: {
const t = text.replace(/\r\n/g, "\n").trim();
const output = ("\n" + t).split(/\n(?=display:\s*\d+\s*\n)/).filter(b => b.startsWith("display:")).map(b => ({
display: Number(b.match(/^display:\s*(\d+)/m)?.[1] ?? -1),
name: (b.match(/^\s*product_name:\s*(.*)$/m)?.[1] ?? "").trim()
})).filter(d => d.display > 0);
root.ddcServiceMon = output;
}
}
}
CustomShortcut {
description: "Increase brightness"
name: "brightnessUp"
@@ -183,16 +166,12 @@ Singleton {
id: monitor
property real brightness
readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.busNum ?? ""
readonly property string displayNum: root.ddcServiceMon.find(m => m.name === modelData.model)?.display ?? ""
readonly property string busNum: ddcInfo?.busNum ?? ""
readonly property var ddcInfo: root.ddcMonitorMap[modelData.name] ?? null
readonly property Process initProc: Process {
stdout: StdioCollector {
onStreamFinished: {
if (monitor.isDdcService) {
const output = text.split("\n").filter(o => o.startsWith("vcp_current_value:"))[0].split(":")[1];
const val = parseInt(output.trim());
monitor.brightness = val / 100;
} else if (monitor.isAppleDisplay) {
if (monitor.isAppleDisplay) {
const val = parseInt(text.trim());
monitor.brightness = val / 101;
} else {
@@ -203,12 +182,11 @@ Singleton {
}
}
readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
readonly property bool isDdc: root.ddcMonitors.some(m => m.connector === modelData.name)
readonly property bool isDdcService: Config.services.ddcutilService
readonly property bool isDdc: ddcInfo !== null
required property ShellScreen modelData
property real queuedBrightness: NaN
readonly property Timer timer: Timer {
interval: 500
interval: 400
onTriggered: {
if (!isNaN(monitor.queuedBrightness)) {
@@ -219,9 +197,7 @@ Singleton {
}
function initBrightness(): void {
if (isDdcService)
initProc.command = ["ddcutil-client", "-d", displayNum, "getvcp", "10"];
else if (isAppleDisplay)
if (isAppleDisplay)
initProc.command = ["asdbctl", "get"];
else if (isDdc)
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
@@ -237,28 +213,25 @@ Singleton {
if (Math.round(brightness * 100) === rounded)
return;
if ((isDdc || isDdcService) && timer.running) {
if (isDdc && timer.running) {
queuedBrightness = value;
return;
}
brightness = value;
if (isDdcService)
Quickshell.execDetached(["ddcutil-client", "-d", displayNum, "setvcp", "10", rounded]);
else if (isAppleDisplay)
if (isAppleDisplay)
Quickshell.execDetached(["asdbctl", "set", rounded]);
else if (isDdc)
Quickshell.execDetached(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", ".1", "--skip-ddc-checks", "-b", busNum, "setvcp", "10", rounded]);
else
Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]);
if (isDdc || isDdcService)
if (isDdc)
timer.restart();
}
Component.onCompleted: initBrightness()
onBusNumChanged: initBrightness()
onDisplayNumChanged: initBrightness()
}
}
-97
View File
@@ -1,97 +0,0 @@
pragma Singleton
import Quickshell
import qs.Helpers
Singleton {
id: root
property int displayMonth: new Date().getMonth()
property int displayYear: new Date().getFullYear()
readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday
function getISOWeekNumber(date: var): int {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
function getWeekNumbers(month: int, year: int): var {
const days = getWeeksForMonth(month, year);
const weekNumbers = [];
let lastWeekNumber = -1;
for (let i = 0; i < days.length; i++) {
// Only add week numbers for days that belong to the current month
if (days[i].isCurrentMonth) {
const dayDate = new Date(days[i].year, days[i].month, days[i].day);
const weekNumber = getISOWeekNumber(dayDate);
// Only push if this is a new week
if (weekNumber !== lastWeekNumber) {
weekNumbers.push(weekNumber);
lastWeekNumber = weekNumber;
}
}
}
return weekNumbers;
}
function getWeekStartIndex(month: int, year: int): int {
const today = new Date();
if (today.getMonth() !== month || today.getFullYear() !== year) {
return 0;
}
const days = getWeeksForMonth(month, year);
for (let i = 0; i < days.length; i++) {
if (days[i].isToday) {
// Return the start index of the week containing today
return Math.floor(i / 7) * 7;
}
}
return 0;
}
function getWeeksForMonth(month: int, year: int): var {
const firstDayOfMonth = new Date(year, month, 1);
const lastDayOfMonth = new Date(year, month + 1, 0);
const days = [];
let currentDate = new Date(year, month, 1);
// Back up to the start of the first week (Sunday or Monday based on locale)
const dayOfWeek = firstDayOfMonth.getDay();
const daysToBackup = (dayOfWeek - root.weekStartDay + 7) % 7;
currentDate.setDate(currentDate.getDate() - daysToBackup);
// Collect all days, including padding from previous/next month to complete the weeks
while (true) {
days.push({
day: currentDate.getDate(),
month: currentDate.getMonth(),
year: currentDate.getFullYear(),
isCurrentMonth: currentDate.getMonth() === month,
isToday: isDateToday(currentDate)
});
currentDate.setDate(currentDate.getDate() + 1);
// Stop after we've completed a full week following the last day of the month
if (currentDate > lastDayOfMonth && days.length % 7 === 0) {
break;
}
}
return days;
}
function isDateToday(date: var): bool {
const today = new Date();
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
}
}
-17
View File
@@ -1,17 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
icon = icon.replace("qspixmap", "icon/discord-tray");
}
return icon;
}
}
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Hyprland
import qs.Helpers
Singleton {
function getInitialTitle(callback) {
let activeWindow = Hypr.activeToplevel.title;
let activeClass = Hypr.activeToplevel.lastIpcObject.class.toString();
let regex = new RegExp(activeClass, "i");
const evalTitle = activeWindow.match(regex);
callback(evalTitle);
}
}
+1 -7
View File
@@ -9,31 +9,25 @@ import qs.Config
Singleton {
id: root
// Private properties
property real _downloadSpeed: 0
property real _downloadTotal: 0
// Initial readings for calculating totals
property real _initialRxBytes: 0
property real _initialTxBytes: 0
property bool _initialized: false
// Previous readings for calculating speed
property real _prevRxBytes: 0
property real _prevTimestamp: 0
property real _prevTxBytes: 0
property real _uploadSpeed: 0
property real _uploadTotal: 0
// History buffers for sparkline
readonly property CircularBuffer downloadBuffer: _downloadBuffer
// Current speeds in bytes per second
readonly property real downloadSpeed: _downloadSpeed
// Total bytes transferred since tracking started
readonly property real downloadTotal: _downloadTotal
readonly property int historyLength: 30
readonly property int historyLength: Config.dashboard.performance.showVram ? 70 : 30
property int refCount: 0
readonly property CircularBuffer uploadBuffer: _uploadBuffer
readonly property real uploadSpeed: _uploadSpeed
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias centerX: notifCenterSpacing.centerX
JsonAdapter {
id: notifCenterSpacing
property int centerX
}
}
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell.Io
import Quickshell
Singleton {
id: root
property alias notifPath: storage.notifPath
JsonAdapter {
id: storage
property string notifPath: Quickshell.statePath("notifications.json")
}
}
-6
View File
@@ -1,6 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
}
+31 -3
View File
@@ -1,12 +1,12 @@
pragma Singleton
import QtQml
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import QtQml
import ZShell
import qs.Config
import qs.Components
import qs.Config
Singleton {
id: root
@@ -15,7 +15,24 @@ Singleton {
readonly property list<MprisPlayer> list: Mpris.players.values
property alias manualActive: props.manualActive
function getArtUrl(player: MprisPlayer): string {
if (!player)
return "";
if (player.trackArtUrl)
return player.trackArtUrl;
const url = player.metadata["xesam:url"] ?? "";
if (url.startsWith("https://www.youtube.com/watch")) {
// Fallback for youtube
const id = url.match(/[?&]v=([\w-]{11})/)?.[1];
return id ? `https://img.youtube.com/vi/${id}/hqdefault.jpg` : "";
}
return "";
}
function getIdentity(player: MprisPlayer): string {
if (!player)
return "";
const alias = Config.services.playerAliases.find(a => a.from === player.identity);
return alias?.to ?? player.identity;
}
@@ -25,9 +42,12 @@ Singleton {
if (!Config.utilities.toasts.nowPlaying) {
return;
}
if (root.active.trackArtist != "" && root.active.trackTitle != "") {
Toaster.toast(qsTr("Now Playing"), qsTr("%1 - %2").arg(root.active.trackArtist).arg(root.active.trackTitle), "music_note");
}
}
target: active
target: root.active
}
PersistentProperties {
@@ -38,8 +58,10 @@ Singleton {
reloadableId: "players"
}
// qmllint disable unresolved-type
CustomShortcut {
description: "Toggle media playback"
// qmllint enable unresolved-type
name: "mediaToggle"
onPressed: {
@@ -49,8 +71,10 @@ Singleton {
}
}
// qmllint disable unresolved-type
CustomShortcut {
description: "Previous track"
// qmllint enable unresolved-type
name: "mediaPrev"
onPressed: {
@@ -60,8 +84,10 @@ Singleton {
}
}
// qmllint disable unresolved-type
CustomShortcut {
description: "Next track"
// qmllint enable unresolved-type
name: "mediaNext"
onPressed: {
@@ -71,8 +97,10 @@ Singleton {
}
}
// qmllint disable unresolved-type
CustomShortcut {
description: "Stop media playback"
// qmllint enable unresolved-type
name: "mediaStop"
onPressed: root.active?.stop()
-49
View File
@@ -1,49 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import ZShell.Models
import qs.Config
import qs.Modules
import qs.Helpers
import qs.Paths
Searcher {
id: root
property string actualCurrent: WallpaperPath.currentWallpaperPath
readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath
property bool showPreview: false
function preview(path: string): void {
previewPath = path;
showPreview = true;
}
function setWallpaper(path: string): void {
actualCurrent = path;
WallpaperPath.currentWallpaperPath = path;
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/LockScreenBg.py")} --input_image=${root.actualCurrent} --output_path=${Paths.state}/lockscreen_bg.png`]);
}
function stopPreview(): void {
showPreview = false;
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/SchemeColorGen.py")} --path=${root.actualCurrent} --thumbnail=${Paths.cache}/imagecache/thumbnail.jpg --output=${Paths.state}/scheme.json --scheme=${Config.colors.schemeType}`]);
}
extraOpts: useFuzzy ? ({}) : ({
forward: false
})
key: "relativePath"
list: wallpapers.entries
useFuzzy: true
FileSystemModel {
id: wallpapers
filter: FileSystemModel.Images
path: Config.general.wallpaperPath
recursive: true
}
}
-60
View File
@@ -1,60 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property Item activeMenu: null
property Item activeTrigger: null
function close(menu) {
if (!menu)
return;
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
menu.expanded = false;
}
function closeActive() {
if (activeMenu)
activeMenu.expanded = false;
activeMenu = null;
activeTrigger = null;
}
function forget(menu) {
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
}
function hit(item, scenePos) {
if (!item || !item.visible)
return false;
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
return item.contains(p);
}
function open(menu, trigger) {
if (activeMenu && activeMenu !== menu)
activeMenu.expanded = false;
activeMenu = menu;
activeTrigger = trigger || null;
menu.expanded = true;
}
function toggle(menu, trigger) {
if (activeMenu === menu && menu.expanded)
close(menu);
else
open(menu, trigger);
}
}
-27
View File
@@ -1,27 +0,0 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property string highlightedSetting: ""
function clear() {
highlightedSetting = "";
}
function highlight(settingName: string) {
highlightedSetting = settingName;
highlightTimer.restart();
}
Timer {
id: highlightTimer
interval: 2000
onTriggered: root.clear()
}
}
-6
View File
@@ -1,6 +0,0 @@
import QtQuick
Text {
renderType: Text.NativeRendering
textFormat: Text.PlainText
}
-288
View File
@@ -1,288 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
property list<DesktopEntry> entryList: []
property var preppedIcons: []
property var preppedIds: []
property var preppedNames: []
// Dynamic fixups
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property real scoreThreshold: 0.2
// Manual overrides for tricky apps
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot"
})
function checkCleanMatch(str) {
if (!str || str.length <= 3)
return null;
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
// Aggressive fallback: strip all separators
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
for (let i = 0; i < list.length; i++) {
const entry = list[i];
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
return entry;
}
}
return null;
}
function checkFuzzySearch(str) {
if (typeof FuzzySort === 'undefined')
return null;
// Check filenames (IDs) first
if (preppedIds.length > 0) {
let results = fuzzyQuery(str, preppedIds);
if (results.length === 0) {
const underscored = str.replace(/-/g, '_').toLowerCase();
if (underscored !== str)
results = fuzzyQuery(underscored, preppedIds);
}
if (results.length > 0)
return results[0];
}
// Then icons
if (preppedIcons.length > 0) {
const results = fuzzyQuery(str, preppedIcons);
if (results.length > 0)
return results[0];
}
// Then names
if (preppedNames.length > 0) {
const results = fuzzyQuery(str, preppedNames);
if (results.length > 0)
return results[0];
}
return null;
}
// --- Lookup Helpers ---
function checkHeuristic(str) {
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
const lower = str.toLowerCase();
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
for (let i = 0; i < variants.length; i++) {
const variant = variants[i];
if (variant) {
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
}
}
return null;
}
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function fuzzyQuery(search, preppedData) {
if (!search || !preppedData || preppedData.length === 0)
return [];
return FuzzySort.go(search, preppedData, {
all: true,
key: "name"
}).map(r => r.obj.entry);
}
function getFromReverseDomain(str) {
if (!str)
return "";
return str.split('.').slice(-1)[0];
}
// Deprecated shim
function guessIcon(str) {
const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
}
function iconExists(iconName) {
if (!iconName || iconName.length === 0)
return false;
if (iconName.startsWith("/"))
return true;
const path = Quickshell.iconPath(iconName, true);
return path && path.length > 0 && !path.includes("image-missing");
}
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable";
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return "";
}
}
function normalizeWithHyphens(str) {
if (!str)
return "";
return str.toLowerCase().replace(/\s+/g, "-");
}
function refreshEntries() {
if (typeof DesktopEntries === 'undefined')
return;
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `),
entry: a
}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
Component.onCompleted: refreshEntries()
Connections {
function onValuesChanged() {
refreshEntries();
}
target: DesktopEntries.applications
}
}
+107 -32
View File
@@ -1,7 +1,7 @@
pragma Singleton
import Quickshell
import QtQuick
import Quickshell
import ZShell
import qs.Config
@@ -12,15 +12,15 @@ Singleton {
property var cc
property string city
readonly property string description: cc?.weatherDesc ?? qsTr("No weather")
readonly property string feelsLike: `${cc?.feelsLikeC ?? 0}°C`
readonly property string feelsLike: formatTemp(cc?.feelsLikeC)
property list<var> forecast
property list<var> hourlyForecast
readonly property int humidity: cc?.humidity ?? 0
readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
property string loc
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), "h:mm") : "--:--"
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), "h:mm") : "--:--"
readonly property string temp: `${cc?.tempC ?? 0}°C`
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
readonly property string temp: formatTemp(cc?.tempC)
readonly property real windSpeed: cc?.windSpeed ?? 0
function fetchCityFromCoords(coords: string): void {
@@ -29,29 +29,48 @@ Singleton {
return;
}
const [lat, lon] = coords.split(",");
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=geocodejson`;
Requests.get(url, text => {
const [lat, lon] = coords.split(",").map(s => s.trim());
const lang = Qt.locale().name.split("_")[0] || "en";
const fallbackToBigDataCloud = () => {
const fallbackUrl = `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=${lang}`;
Requests.get(fallbackUrl, text => {
const geo = JSON.parse(text);
const geoCity = geo.city || geo.locality;
if (geoCity) {
city = fixCityName(geoCity);
cachedCities.set(coords, city);
} else {
city = "Unknown City";
}
});
};
const nominatimUrl = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=geocodejson&accept-language=${lang}`;
Requests.get(nominatimUrl, text => {
const geo = JSON.parse(text).features?.[0]?.properties.geocoding;
if (geo) {
const geoCity = geo.type === "city" ? geo.name : geo.city;
city = geoCity;
cachedCities.set(coords, geoCity);
} else {
city = "Unknown City";
if (geoCity) {
city = fixCityName(geoCity);
cachedCities.set(coords, city);
return;
}
}
});
fallbackToBigDataCloud();
}, fallbackToBigDataCloud);
}
function fetchCoordsFromCity(cityName: string): void {
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=en&format=json`;
const lang = Qt.locale().name.split("_")[0] || "en";
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=${lang}&format=json`;
Requests.get(url, text => {
const json = JSON.parse(text);
if (json.results && json.results.length > 0) {
const result = json.results[0];
loc = result.latitude + "," + result.longitude;
city = result.name;
city = fixCityName(result.name);
} else {
loc = "";
reload();
@@ -72,25 +91,21 @@ Singleton {
cc = {
weatherCode: json.current.weather_code,
weatherDesc: getWeatherCondition(json.current.weather_code),
tempC: Math.round(json.current.temperature_2m),
tempF: Math.round(toFahrenheit(json.current.temperature_2m)),
feelsLikeC: Math.round(json.current.apparent_temperature),
feelsLikeF: Math.round(toFahrenheit(json.current.apparent_temperature)),
tempC: json.current.temperature_2m,
feelsLikeC: json.current.apparent_temperature,
humidity: json.current.relative_humidity_2m,
windSpeed: json.current.wind_speed_10m,
isDay: json.current.is_day,
sunrise: json.daily.sunrise[0],
sunset: json.daily.sunset[0]
sunrise: json.daily.sunrise[0].replace("T", " "),
sunset: json.daily.sunset[0].replace("T", " ")
};
const forecastList = [];
for (let i = 0; i < json.daily.time.length; i++)
forecastList.push({
date: json.daily.time[i],
maxTempC: Math.round(json.daily.temperature_2m_max[i]),
maxTempF: Math.round(toFahrenheit(json.daily.temperature_2m_max[i])),
minTempC: Math.round(json.daily.temperature_2m_min[i]),
minTempF: Math.round(toFahrenheit(json.daily.temperature_2m_min[i])),
date: json.daily.time[i].replace(/-/g, "/"),
maxTempC: json.daily.temperature_2m_max[i],
minTempC: json.daily.temperature_2m_min[i],
weatherCode: json.daily.weather_code[i],
icon: Icons.getWeatherIcon(json.daily.weather_code[i])
});
@@ -99,7 +114,8 @@ Singleton {
const hourlyList = [];
const now = new Date();
for (let i = 0; i < json.hourly.time.length; i++) {
const time = new Date(json.hourly.time[i]);
const time = new Date(json.hourly.time[i].replace("T", " "));
if (time < now)
continue;
@@ -107,7 +123,7 @@ Singleton {
timestamp: json.hourly.time[i],
hour: time.getHours(),
tempC: Math.round(json.hourly.temperature_2m[i]),
tempF: Math.round(toFahrenheit(json.hourly.temperature_2m[i])),
precipChance: json.hourly.precipitation_probability[i],
weatherCode: json.hourly.weather_code[i],
icon: Icons.getWeatherIcon(json.hourly.weather_code[i])
});
@@ -116,6 +132,59 @@ Singleton {
});
}
function fixCityName(cityName: string): string {
if (!cityName)
return "";
const mapping = {
// Polish
"Poznan": "Poznań",
"Wroclaw": "Wrocław",
"Krakow": "Kraków",
"Gdansk": "Gdańsk",
"Lodz": "Łódź",
"Rzeszow": "Rzeszów",
"Torun": "Toruń",
"Bialystok": "Białystok",
"Czestochowa": "Częstochowa",
"Plock": "Płock",
"Ruda Slaska": "Ruda Śląska",
"Dabrowa Gornicza": "Dąbrowa Górnicza",
"Elblag": "Elbląg",
"Gorzow Wielkopolski": "Gorzów Wielkopolski",
"Zielona Gora": "Zielona Góra",
"Slupsk": "Słupsk",
// German
"Munchen": "München",
"Koln": "Köln",
"Dusseldorf": "Düsseldorf",
"Nurnberg": "Nürnberg",
// French & Spanish & Portuguese
"Sao Paulo": "São Paulo",
"Montreal": "Montréal",
"Quebec": "Québec",
"Bogota": "Bogotá",
"Medellin": "Medellín",
"Cordoba": "Córdoba",
// Turkish
"Istanbul": "İstanbul",
"Izmir": "İzmir",
// Scandinavian & others
"Malmo": "Malmö",
"Goteborg": "Göteborg",
"Zurich": "Zürich",
"Geneve": "Genève"
};
return mapping[cityName] || cityName;
}
function formatTemp(temp: var): string {
return Config.services.useFahrenheit ? `${temp !== undefined ? Math.round(toFahrenheit(temp)) : "--"}°F` : `${temp !== undefined ? Math.round(temp) : "--"}°C`;
}
function getWeatherCondition(code: string): string {
const conditions = {
"0": "Clear",
@@ -154,9 +223,9 @@ Singleton {
if (!loc || loc.indexOf(",") === -1)
return "";
const [lat, lon] = loc.split(",");
const [lat, lon] = loc.split(",").map(s => s.trim());
const baseUrl = "https://api.open-meteo.com/v1/forecast";
const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"];
const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m,precipitation_probability", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"];
return baseUrl + "?" + params.join("&");
}
@@ -189,7 +258,14 @@ Singleton {
onLocChanged: fetchWeatherData()
// Refresh current location hourly
Connections {
function onWeatherLocationChanged(): void {
root.reload();
}
target: Config.services
}
Timer {
interval: 3600000 // 1 hour
repeat: true
@@ -200,6 +276,5 @@ Singleton {
ElapsedTimer {
id: timer
}
}
-56
View File
@@ -1,56 +0,0 @@
import Quickshell.Io
JsonObject {
property list<var> week_0: [0, 0, 0, 0, 0, 0,]
property list<var> week_1: [0, 0, 0, 0, 0, 0,]
property list<var> week_10: [0, 0, 0, 0, 0, 0,]
property list<var> week_11: [0, 0, 0, 0, 0, 0,]
property list<var> week_12: [0, 0, 0, 0, 0, 0,]
property list<var> week_13: [0, 0, 0, 0, 0, 0,]
property list<var> week_14: [0, 0, 0, 0, 0, 0,]
property list<var> week_15: [0, 0, 0, 0, 0, 0,]
property list<var> week_16: [0, 0, 0, 0, 0, 0,]
property list<var> week_17: [0, 0, 0, 0, 0, 0,]
property list<var> week_18: [0, 0, 0, 0, 0, 0,]
property list<var> week_19: [0, 0, 0, 0, 0, 0,]
property list<var> week_2: [0, 0, 0, 0, 0, 0,]
property list<var> week_20: [0, 0, 0, 0, 0, 0,]
property list<var> week_21: [0, 0, 0, 0, 0, 0,]
property list<var> week_22: [0, 0, 0, 0, 0, 0,]
property list<var> week_23: [0, 0, 0, 0, 0, 0,]
property list<var> week_24: [0, 0, 0, 0, 0, 0,]
property list<var> week_25: [0, 0, 0, 0, 0, 0,]
property list<var> week_26: [0, 0, 0, 0, 0, 0,]
property list<var> week_27: [0, 0, 0, 0, 0, 0,]
property list<var> week_28: [0, 0, 0, 0, 0, 0,]
property list<var> week_29: [0, 0, 0, 0, 0, 0,]
property list<var> week_3: [0, 0, 0, 0, 0, 0,]
property list<var> week_30: [0, 0, 0, 0, 0, 0,]
property list<var> week_31: [0, 0, 0, 0, 0, 0,]
property list<var> week_32: [0, 0, 0, 0, 0, 0,]
property list<var> week_33: [0, 0, 0, 0, 0, 0,]
property list<var> week_34: [0, 0, 0, 0, 0, 0,]
property list<var> week_35: [0, 0, 0, 0, 0, 0,]
property list<var> week_36: [0, 0, 0, 0, 0, 0,]
property list<var> week_37: [0, 0, 0, 0, 0, 0,]
property list<var> week_38: [0, 0, 0, 0, 0, 0,]
property list<var> week_39: [0, 0, 0, 0, 0, 0,]
property list<var> week_4: [0, 0, 0, 0, 0, 0,]
property list<var> week_40: [0, 0, 0, 0, 0, 0,]
property list<var> week_41: [0, 0, 0, 0, 0, 0,]
property list<var> week_42: [0, 0, 0, 0, 0, 0,]
property list<var> week_43: [0, 0, 0, 0, 0, 0,]
property list<var> week_44: [0, 0, 0, 0, 0, 0,]
property list<var> week_45: [0, 0, 0, 0, 0, 0,]
property list<var> week_46: [0, 0, 0, 0, 0, 0,]
property list<var> week_47: [0, 0, 0, 0, 0, 0,]
property list<var> week_48: [0, 0, 0, 0, 0, 0,]
property list<var> week_49: [0, 0, 0, 0, 0, 0,]
property list<var> week_5: [0, 0, 0, 0, 0, 0,]
property list<var> week_50: [0, 0, 0, 0, 0, 0,]
property list<var> week_51: [0, 0, 0, 0, 0, 0,]
property list<var> week_6: [0, 0, 0, 0, 0, 0,]
property list<var> week_7: [0, 0, 0, 0, 0, 0,]
property list<var> week_8: [0, 0, 0, 0, 0, 0,]
property list<var> week_9: [0, 0, 0, 0, 0, 0,]
}
+3 -19
View File
@@ -3,12 +3,9 @@ pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules
import qs.Config
import qs.Helpers
import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets
import qs.Modules.Network
import qs.Modules.Updates
@@ -26,7 +23,7 @@ RowLayout {
const ch = childAt(x, height / 2) as WrappedLoader;
if (!ch || ch?.id === "spacer") {
if (!popouts.currentName.startsWith("traymenu"))
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover)
popouts.hasCurrent = false;
return;
}
@@ -48,7 +45,7 @@ RowLayout {
return;
}
if (!popouts.currentName.startsWith("traymenu"))
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover)
popouts.hasCurrent = false;
}
@@ -57,19 +54,7 @@ RowLayout {
const item = ch.item;
const itemWidth = item.implicitWidth;
if (id === "audio" && Config.bar.popouts.audio) {
popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "network" && Config.bar.popouts.network) {
popouts.currentName = "network";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "upower" && Config.bar.popouts.upower) {
popouts.currentName = "upower";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "updates") {
if (id === "updates") {
popouts.currentName = "updates";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
@@ -81,7 +66,6 @@ RowLayout {
Repeater {
id: repeater
// model: Config.bar.entries.filted(n => n.index > 50).sort(n => n.index)
model: Config.bar.entries
DelegateChooser {
+4 -13
View File
@@ -34,21 +34,11 @@ Item {
implicitHeight: 50
radius: Appearance.rounding.full
MaterialIcon {
id: searchIcon
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
text: "search"
}
CustomTextField {
SearchBar {
id: searchField
anchors.bottom: parent.bottom
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.palette.m3onSurface
@@ -170,7 +160,6 @@ Item {
id: lineText
color: DynamicColors.palette.m3onSurface
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
height: lineText.paintedHeight + Appearance.padding.extraSmall * 2
text: lineRow.modelData.text.trim()
verticalAlignment: Text.AlignVCenter
@@ -336,6 +325,8 @@ Item {
}
model: ScriptModel {
values: ClipHistory.fuzzyQuery(searchField.text)
onValuesChanged: view.currentIndex = 0
}
onCurrentItemChanged: {
+1 -5
View File
@@ -52,11 +52,7 @@ Item {
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
topPadding: Appearance.padding.larger
Component.onCompleted: {
console.log(search.color);
console.log(search.placeholderTextColor);
forceActiveFocus();
}
Component.onCompleted: forceActiveFocus()
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false
Keys.onPressed: event => {
+1 -1
View File
@@ -15,7 +15,7 @@ Item {
if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
max -= panels.resources.nonAnimHeight;
if (panels.popouts.hasCurrent)
if (panels.popouts.current.x + panels.popouts.current.width > root.x && panels.popouts.current.x < root.x + root.width)
if (panels.popouts.current?.x + panels.popouts.current?.width > root.x && panels.popouts.current?.x < root.x + root.width)
max -= panels.popouts.nonAnimHeight;
return max;
}
+31 -41
View File
@@ -4,76 +4,66 @@ import qs.Components
import qs.Helpers
import qs.Config
RowLayout {
CustomClippingRect {
id: root
required property var lock
spacing: Appearance.spacing.large * 2
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
radius: Appearance.rounding.large
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
RowLayout {
id: layout
CustomRect {
anchors.fill: parent
spacing: Appearance.spacing.large * 2
ColumnLayout {
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: weather.implicitHeight
radius: Appearance.rounding.small
topLeftRadius: Appearance.rounding.large
spacing: Appearance.spacing.normal
WeatherInfo {
id: weather
Layout.fillWidth: true
rootHeight: root.height
}
}
CustomRect {
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: resources.implicitHeight
radius: Appearance.rounding.small
Resources {
id: resources
Layout.fillWidth: true
}
}
CustomClippingRect {
Layout.fillHeight: true
Layout.fillWidth: true
bottomLeftRadius: Appearance.rounding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.small
Media {
id: media
Layout.fillHeight: true
Layout.fillWidth: true
lock: root.lock
}
}
}
Center {
lock: root.lock
}
Center {
lock: root.lock
}
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
CustomRect {
Layout.fillHeight: true
ColumnLayout {
Layout.fillWidth: true
bottomRightRadius: Appearance.rounding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.small
topRightRadius: Appearance.rounding.large
spacing: Appearance.spacing.normal
NotifDock {
lock: root.lock
CustomRect {
Layout.fillHeight: true
Layout.fillWidth: true
bottomRightRadius: Appearance.rounding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.small
topRightRadius: Appearance.rounding.large
NotifDock {
lock: root.lock
}
}
}
}
+49 -14
View File
@@ -149,36 +149,71 @@ WlSessionLockSurface {
Image {
id: background
anchors.bottomMargin: -8 - lockContent.positions[lockContent.positionIndex].y
anchors.fill: parent
anchors.leftMargin: -8 + lockContent.positions[lockContent.positionIndex].x
anchors.rightMargin: -8 - lockContent.positions[lockContent.positionIndex].x
anchors.topMargin: -8 + lockContent.positions[lockContent.positionIndex].y
fillMode: Image.PreserveAspectCrop
source: WallpaperPath.lockscreenBg
Behavior on anchors.bottomMargin {
Anim {
duration: 5000
}
}
Behavior on anchors.leftMargin {
Anim {
duration: 5000
}
}
Behavior on anchors.rightMargin {
Anim {
duration: 5000
}
}
Behavior on anchors.topMargin {
Anim {
duration: 5000
}
}
}
Item {
id: lockContent
property int positionIndex: 0
readonly property var positions: [Qt.point(0, 0), Qt.point(4, 0), Qt.point(4, 4), Qt.point(0, 4), Qt.point(-4, 4), Qt.point(-4, 0), Qt.point(-4, -4), Qt.point(0, -4), Qt.point(4, -4),]
readonly property int radius: size / 4 * Appearance.rounding.scale
readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
anchors.centerIn: parent
anchors.horizontalCenterOffset: positions[positionIndex].x
anchors.verticalCenterOffset: positions[positionIndex].y
implicitHeight: size
implicitWidth: size
scale: 0
// MultiEffect {
// anchors.fill: lockBg
// autoPaddingEnabled: false
// blur: 1
// blurEnabled: true
// blurMax: 64
// maskEnabled: true
// maskSource: lockBg
//
// source: ShaderEffectSource {
// sourceItem: background
// sourceRect: Qt.rect(lockBg.x, lockBg.y, lockBg.width, lockBg, height)
// }
// }
Behavior on anchors.horizontalCenterOffset {
Anim {
duration: 5000
}
}
Behavior on anchors.verticalCenterOffset {
Anim {
duration: 5000
}
}
Timer {
interval: 120000
repeat: true
running: true
onTriggered: {
lockContent.positionIndex = (lockContent.positionIndex + 1) % lockContent.positions.length;
}
}
CustomRect {
id: lockBg
+54 -145
View File
@@ -1,201 +1,110 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Modules
import Quickshell
import ZShell.Components
import qs.Components
import qs.Helpers
import qs.Config
import qs.Helpers
Item {
CustomClippingRect {
id: root
required property var lock
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
radius: Appearance.rounding.small
FadeImage {
id: image
Image {
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
layer.enabled: true
opacity: status === Image.Ready ? 1 : 0
source: Players.active?.trackArtUrl ?? ""
sourceSize.height: height
sourceSize.width: width
layer.effect: OpacityMask {
maskSource: mask
source: Players.getArtUrl(Players.active)
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.extraLarge
type: Anim.StandardExtraLarge
}
}
}
Rectangle {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(0, 0, 0, 0.5)
position: 0
}
GradientStop {
color: Qt.rgba(0, 0, 0, 0.2)
position: 0.4
}
GradientStop {
color: Qt.rgba(0, 0, 0, 0)
position: 0.8
}
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3surface
opacity: 0.7
}
}
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.large
CustomText {
Layout.bottomMargin: Appearance.spacing.larger
Layout.topMargin: Appearance.padding.large
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
font.weight: 500
text: qsTr("Now playing")
}
anchors.left: parent.left
anchors.margins: Appearance.padding.extraLarge
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.extraSmall
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3primary
elide: Text.ElideRight
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.large
font.weight: 600
font.pointSize: Appearance.font.size.medium
horizontalAlignment: Text.AlignHCenter
text: Players.active?.trackArtist ?? qsTr("No media")
text: (Players.active?.trackTitle ?? qsTr("Nothing playing")) || qsTr("Unknown track")
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.larger
font.pointSize: Appearance.font.size.small
horizontalAlignment: Text.AlignHCenter
text: Players.active?.trackTitle ?? qsTr("No media")
text: (Players.active?.trackArtist ?? qsTr("Try playing some music!")) || qsTr("Unknown artist")
}
RowLayout {
ButtonRow {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.spacing.large * 1.2
spacing: Appearance.spacing.large
PlayerControl {
function onClicked(): void {
if (Players.active?.canGoPrevious)
Players.active.previous();
}
Layout.topMargin: Appearance.spacing.small
spacing: Appearance.spacing.extraSmall
IconButton {
enabled: Players.active?.canGoPrevious
icon: "skip_previous"
isRound: true
shapeMorph: true
type: IconButton.Tonal
onClicked: Players.active?.previous()
}
PlayerControl {
function onClicked(): void {
if (Players.active?.canTogglePlaying)
Players.active.togglePlaying();
}
IconButton {
checked: Players.active?.isPlaying ?? false
enabled: Players.active?.canTogglePlaying
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
implicitWidth: implicitHeight + Appearance.padding.largeIncreased * 2
isRound: true
shapeMorph: true
active: Players.active?.isPlaying ?? false
animate: true
icon: active ? "pause" : "play_arrow"
level: active ? 2 : 1
set_color: "Primary"
onClicked: Players.active?.togglePlaying()
}
PlayerControl {
function onClicked(): void {
if (Players.active?.canGoNext)
Players.active.next();
}
IconButton {
enabled: Players.active?.canGoNext
icon: "skip_next"
}
}
}
isRound: true
shapeMorph: true
type: IconButton.Tonal
component PlayerControl: CustomRect {
id: control
property bool active
property alias animate: controlIcon.animate
property alias icon: controlIcon.text
property int level: 1
property string set_color: "Secondary"
function onClicked(): void {
}
Layout.preferredWidth: implicitWidth + (controlState.pressed ? Appearance.padding.normal * 2 : active ? Appearance.padding.small * 2 : 0)
color: active ? DynamicColors.palette[`m3${set_color.toLowerCase()}`] : DynamicColors.palette[`m3${set_color.toLowerCase()}Container`]
implicitHeight: controlIcon.implicitHeight + Appearance.padding.normal * 2
implicitWidth: controlIcon.implicitWidth + Appearance.padding.large * 2
radius: active || controlState.pressed ? Appearance.rounding.small : Appearance.rounding.normal
Behavior on Layout.preferredWidth {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
Behavior on radius {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
Elevation {
anchors.fill: parent
level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level
radius: parent.radius
z: -1
}
StateLayer {
id: controlState
color: control.active ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`]
onClicked: {
control.onClicked();
}
}
MaterialIcon {
id: controlIcon
anchors.centerIn: parent
color: control.active ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`]
fill: control.active ? 1 : 0
font.pointSize: Appearance.font.size.large
Behavior on fill {
Anim {
}
onClicked: Players.active?.next()
}
}
}
+48 -47
View File
@@ -1,81 +1,82 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import M3Shapes
import ZShell.Services
import qs.Components
import qs.Helpers
import qs.Config
import qs.Effects
GridLayout {
CustomRect {
id: root
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
columnSpacing: Appearance.spacing.large
columns: 2
rowSpacing: Appearance.spacing.large
rows: 1
readonly property real fontScale: {
const diff = width / 391 - 1; // 391 is the width at 1080 height screen
return 1 + Math.pow(Math.abs(diff), 0.8) * Math.sign(diff);
}
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
radius: Appearance.rounding.small
ServiceRef {
service: Cpu
}
ServiceRef {
service: Memory
}
ServiceRef {
service: Cpu
service: Storage
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
fgColor: DynamicColors.palette.m3primary
icon: "memory"
value: Cpu.percentage
RowLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.large
Resource {
id: cpu
fgColor: DynamicColors.palette.m3primary
icon: "memory"
value: Cpu.percentage
}
Resource {
fgColor: DynamicColors.palette.m3tertiary
icon: "memory_alt"
value: Memory.percentage
}
Resource {
fgColor: DynamicColors.palette.m3secondary
icon: "hard_disk"
value: Storage.percentage
}
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
fgColor: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: Memory.percentage
}
component Resource: CustomRect {
component Resource: CircularProgress {
id: res
required property color fgColor
required property string icon
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: width
radius: Appearance.rounding.large
implicitSize: width
Behavior on value {
Behavior on clampedVal {
Anim {
duration: Appearance.anim.durations.large
}
}
CircularProgress {
id: circ
anchors.fill: parent
bgColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
fgColor: res.fgColor
padding: Appearance.padding.large * 3
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
value: res.value
}
MaterialIcon {
id: icon
anchors.centerIn: parent
color: res.fgColor
font.pointSize: (circ.arcRadius * 0.7) || 1
font.weight: 600
font.pointSize: Appearance.font.size.extraLarge
text: res.icon
}
}
+62
View File
@@ -0,0 +1,62 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
ColumnLayout {
id: root
required property int rootHeight
spacing: Appearance.spacing.extraSmall
CustomText {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
text: Weather.description
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Appearance.spacing.small
CustomText {
id: temp
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
text: Weather.temp
}
MaterialIcon {
animate: true
color: DynamicColors.palette.m3secondary
text: Weather.icon
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
text: qsTr("Feels like %1").arg(Weather.temp)
visible: root.rootHeight > 550
}
CustomText {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: {
const today = Weather.forecast[0];
return qsTr("High %1 • Low %2").arg(Weather.formatTemp(today?.maxTempC)).arg(Weather.formatTemp(today?.minTempC));
}
visible: root.rootHeight > 550
}
}
+103
View File
@@ -0,0 +1,103 @@
import QtQuick
import QtQuick.Layouts
import M3Shapes
import ZShell
import qs.Components
import qs.Helpers
import qs.Config
CustomRect {
id: root
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: header.anchors.margins + header.implicitHeight + Appearance.spacing.small + layout.implicitHeight + layout.anchors.bottomMargin
radius: Appearance.rounding.small
RowLayout {
id: header
anchors.left: parent.left
anchors.margins: Appearance.padding.largeIncreased
anchors.top: parent.top
spacing: Appearance.spacing.small
MaterialIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
font.pointSize: Appearance.font.size.medium
text: "schedule"
}
CustomText {
id: title
font.pointSize: Appearance.font.size.medium
text: qsTr("Hourly forecast")
}
}
VerticalFadeListView {
id: layout
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.largeIncreased
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
implicitHeight: contentItem.childrenRect.height
model: Weather.hourlyForecast
orientation: VerticalFadeListView.Horizontal
spacing: Appearance.spacing.normal
delegate: ColumnLayout {
id: hour
readonly property var cond: modelData
required property int index
required property var modelData
spacing: Appearance.spacing.extraSmall
MaterialShape {
Layout.alignment: Qt.AlignHCenter
color: Qt.alpha(DynamicColors.palette.m3primary, hour.index === 0 ? 1 : 0)
implicitSize: temp.implicitHeight + Appearance.padding.normal * 2
shape: MaterialShape.Cookie4Sided
Behavior on color {
CAnim {
}
}
CustomText {
id: temp
anchors.centerIn: parent
color: hour.index === 0 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
}
}
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
text: hour.cond.icon
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3primary
text: hour.cond.precipChance + "%"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.extraSmall
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: hour.index === 0 ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), Config.services.useTwelveHourClock ? "ha" : "hh:00")
}
}
}
}
+34 -150
View File
@@ -1,162 +1,23 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Modules.Lock.Weather
import qs.Config
import qs.Components
import qs.Helpers
import qs.Config
ColumnLayout {
CustomRect {
id: root
required property int rootHeight
readonly property bool showForecast: rootHeight >= 700
anchors.left: parent.left
anchors.margins: Appearance.padding.large * 2
anchors.right: parent.right
spacing: Appearance.spacing.small
Loader {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: -Appearance.padding.large
Layout.topMargin: Appearance.padding.large * 2
active: root.rootHeight > 610
visible: active
sourceComponent: CustomText {
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
text: qsTr("Weather")
}
}
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.large
MaterialIcon {
animate: true
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge * 2.5
text: Weather.icon
}
ColumnLayout {
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3secondary
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
font.weight: 500
text: Weather.description
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
text: qsTr("Humidity: %1%").arg(Weather.humidity)
}
}
Loader {
Layout.rightMargin: Appearance.padding.smaller
active: root.width > 400
visible: active
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3primary
elide: Text.ElideLeft
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
horizontalAlignment: Text.AlignRight
text: Weather.temp
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideLeft
font.pointSize: Appearance.font.size.smaller
horizontalAlignment: Text.AlignRight
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
}
}
}
}
Loader {
id: forecastLoader
Layout.bottomMargin: Appearance.padding.large * 2
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.smaller
active: root.rootHeight > 820
visible: active
sourceComponent: RowLayout {
spacing: Appearance.spacing.large
Repeater {
model: {
const forecast = Weather.hourlyForecast;
const count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
if (!forecast)
return Array.from({
length: count
}, () => null);
return forecast.slice(0, count);
}
ColumnLayout {
id: forecastHour
required property var modelData
Layout.fillWidth: true
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.larger
horizontalAlignment: Text.AlignHCenter
text: {
const hour = forecastHour.modelData?.hour ?? 0;
return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
}
}
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
font.pointSize: Appearance.font.size.extraLarge * 1.5
font.weight: 500
text: forecastHour.modelData?.icon ?? "cloud_alert"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.larger
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
}
}
}
}
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: {
const base = brief.implicitHeight + brief.anchors.topMargin;
if (showForecast)
return base + Appearance.spacing.large + forecast.implicitHeight + forecast.anchors.margins;
return base + brief.anchors.topMargin;
}
radius: Appearance.rounding.small
Timer {
interval: 900000 // 15 minutes
@@ -166,4 +27,27 @@ ColumnLayout {
onTriggered: Weather.reload()
}
BriefInfo {
id: brief
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Appearance.padding.extraLarge
rootHeight: root.rootHeight
}
Loader {
id: forecast
active: root.showForecast
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
asynchronous: true
sourceComponent: Forecast {
}
}
}
@@ -133,7 +133,7 @@ CustomRect {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardAccel
easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "scale"
target: listOrControls
to: 0.7
@@ -141,7 +141,7 @@ CustomRect {
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardAccel
easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "opacity"
target: listOrControls
to: 0
@@ -166,7 +166,7 @@ CustomRect {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardDecel
easing.bezierCurve: Appearance.anim.curves.standardDecel
property: "scale"
target: listOrControls
to: 1
@@ -174,7 +174,7 @@ CustomRect {
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardDecel
easing.bezierCurve: Appearance.anim.curves.standardDecel
property: "opacity"
target: listOrControls
to: 1
@@ -217,14 +217,14 @@ CustomRect {
Anim {
duration: Appearance.anim.durations.large
easing: Appearance.anim.curves.emphasizedAccel
easing.bezierCurve: Appearance.anim.curves.emphasizedAccel
from: 1
to: 0
}
Anim {
duration: Appearance.anim.durations.extraLarge
easing: Appearance.anim.curves.emphasizedDecel
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
from: 0
to: 1
}
+6 -8
View File
@@ -102,15 +102,13 @@ Item {
to: 1.0
value: root.brightness
onPressedChanged: {
if (!pressed) {
if (Config.osd.allMonBrightness) {
for (const mon of Brightness.monitors) {
mon.setBrightness(value);
}
} else {
root.monitor?.setBrightness(value);
onMoved: {
if (Config.osd.allMonBrightness) {
for (const mon of Brightness.monitors) {
mon.setBrightness(value);
}
} else {
root.monitor?.setBrightness(value);
}
}
}
+48 -40
View File
@@ -1,8 +1,8 @@
import Quickshell
import Quickshell.Services.Polkit
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets
import ZShell.Components
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
@@ -28,9 +28,7 @@ Scope {
visible: false
Connections {
target: root
onShouldShowChanged: {
function onShouldShowChanged(): void {
if (root.shouldShow) {
panelWindow.visible = true;
openAnim.start();
@@ -38,6 +36,8 @@ Scope {
closeAnim.start();
}
}
target: root
}
Anim {
@@ -243,17 +243,16 @@ Scope {
Layout.preferredWidth: contentRow.implicitWidth
spacing: 8
CustomButton {
IconTextButton {
id: detailsButton
Layout.alignment: Qt.AlignLeft
Layout.preferredHeight: 40
Layout.preferredWidth: 92
bgColor: DynamicColors.palette.m3surfaceContainer
enabled: true
radius: Appearance.rounding.full
icon: "info"
inactiveColor: DynamicColors.palette.m3surfaceContainer
inactiveOnColor: DynamicColors.palette.m3onSurface
isRound: true
shapeMorph: true
text: "Details"
textColor: DynamicColors.palette.m3onSurface
onClicked: {
panelWindow.detailsOpen = !panelWindow.detailsOpen;
@@ -266,41 +265,50 @@ Scope {
Layout.fillWidth: true
}
CustomButton {
id: okButton
ButtonRow {
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40
Layout.preferredWidth: 76
bgColor: DynamicColors.palette.m3primary
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
radius: Appearance.rounding.full
text: "OK"
textColor: DynamicColors.palette.m3onPrimary
spacing: Appearance.spacing.normal
onClicked: {
polkitAgent.flow.submit(passInput.text);
passInput.text = "";
passInput.forceActiveFocus();
IconTextButton {
id: okButton
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
horizontalPadding: Appearance.padding.large
icon: "check"
inactiveColor: DynamicColors.palette.m3primary
inactiveOnColor: DynamicColors.palette.m3onPrimary
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "OK"
onClicked: {
polkitAgent.flow.submit(passInput.text);
passInput.text = "";
passInput.forceActiveFocus();
}
}
}
CustomButton {
id: cancelButton
IconTextButton {
id: cancelButton
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40
Layout.preferredWidth: 76
bgColor: DynamicColors.palette.m3surfaceContainer
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
radius: Appearance.rounding.full
text: "Cancel"
textColor: DynamicColors.palette.m3onSurface
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
horizontalPadding: Appearance.padding.large
icon: "close"
inactiveColor: DynamicColors.palette.m3surfaceContainer
inactiveOnColor: DynamicColors.palette.m3onSurface
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Cancel"
onClicked: {
root.shouldShow = false;
polkitAgent.flow.cancelAuthenticationRequest();
passInput.text = "";
onClicked: {
root.shouldShow = false;
polkitAgent.flow.cancelAuthenticationRequest();
passInput.text = "";
}
}
}
}
+1
View File
@@ -9,6 +9,7 @@ CustomRect {
readonly property color accent: DynamicColors.palette.m3tertiary
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased * 2
-3
View File
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Internal
import qs.Modules.Resources
import qs.Helpers
import qs.Components
import qs.Config
@@ -9,8 +8,6 @@ import qs.Config
CustomRect {
id: root
required property Wrapper wrapper
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 220
implicitWidth: 300
+89
View File
@@ -0,0 +1,89 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Services
import qs.Components
import qs.Config
CustomRect {
id: root
readonly property color accent: DynamicColors.palette.m3tertiary
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased
radius: Appearance.rounding.medium
ServiceRef {
service: Gpu
}
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
RowLayout {
Layout.leftMargin: -Appearance.padding.extraSmall
spacing: Appearance.spacing.small
MaterialIcon {
color: root.accent
fill: 1
text: "memory_alt"
}
CustomText {
text: qsTr("Video memory")
}
}
CircularProgress {
id: circularIndicator
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.large
fgColor: root.accent
implicitSize: usageColumn.implicitHeight + thickness + Appearance.padding.largeIncreased * 2
startAngle: -225
sweepAngle: 270
value: Gpu.memoryUsed / Gpu.memoryTotal
Behavior on clampedVal {
Anim {
}
}
ColumnLayout {
id: usageColumn
anchors.centerIn: parent
anchors.verticalCenterOffset: Appearance.padding.extraSmall
spacing: 0
CustomText {
Layout.alignment: Qt.AlignHCenter
color: root.accent
font.pointSize: Appearance.font.size.large
text: Math.round(circularIndicator.value * 100) + "%"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Used")
}
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: {
const fmt = UsageFmt.formatKib(Gpu.memoryUsed, Gpu.memoryTotal);
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
}
}
}
}
+21 -4
View File
@@ -74,7 +74,7 @@ Item {
RowLayout {
spacing: Appearance.spacing.normal
visible: storageCard.active || networkCard.active || memoryCard.active
visible: storageCard.active || memoryCard.active || vramCard.active || networkCard1.active
WrappedLoader {
id: storageCard
@@ -95,15 +95,32 @@ Item {
}
WrappedLoader {
id: networkCard
id: vramCard
active: Config.dashboard.performance.showNetwork
active: Config.dashboard.performance.showVram
sourceComponent: VramCard {
}
}
WrappedLoader {
id: networkCard1
active: Config.dashboard.performance.showNetwork && !vramCard.active
sourceComponent: NetworkCard {
wrapper: root.wrapper
}
}
}
WrappedLoader {
id: networkCard2
active: Config.dashboard.performance.showNetwork && vramCard.active
sourceComponent: NetworkCard {
}
}
}
WrappedLoader {
+52
View File
@@ -3,12 +3,64 @@ import qs.Components
import qs.Config
CustomRect {
id: root
property bool first
property bool last
property string settingAnchor
function flashHighlight(): void {
flash.restart();
}
bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.tPalette.m3surfaceContainer
topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
CustomRect {
id: highlight
anchors.fill: parent
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
color: DynamicColors.palette.m3primary
opacity: 0
radius: parent.radius
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
SequentialAnimation {
id: flash
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: highlight
to: 0.2
}
Anim {
duration: Appearance.anim.durations.normal
property: "opacity"
target: highlight
to: 0.08
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: highlight
to: 0.2
}
Anim {
duration: Appearance.anim.durations.extraLarge
property: "opacity"
target: highlight
to: 0
}
}
}
}
+100 -2
View File
@@ -9,6 +9,7 @@ import qs.Config
ColumnLayout {
id: root
property bool animateScroll: false
readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild
readonly property alias flickable: flickable
@@ -16,11 +17,100 @@ ColumnLayout {
required property SettingsState sState
required property string title
function applySearchAnchor(): void {
if (!sState.searchAnchor)
return;
scrollRetry.tries = 0;
scrollRetry.lastHeight = -1;
scrollRetry.stableFrames = 0;
scrollRetry.restart();
}
function findAnchor(item: Item, anchor: string): Item {
if (!item)
return null;
if (item.settingAnchor !== undefined && item.settingAnchor === anchor)
return item;
const kids = item.children;
for (let i = 0; i < kids.length; i++) {
const found = findAnchor(kids[i], anchor);
if (found)
return found;
}
return null;
}
function highlightAnchor(anchor: string): void {
const row = findAnchor(contentChild, anchor);
if (row && row.flashHighlight !== undefined)
row.flashHighlight();
}
function scrollToAnchor(anchor: string): bool {
if (!anchor || !contentChild)
return false;
const row = findAnchor(contentChild, anchor);
if (!row)
return false;
const pos = row.mapToItem(flickable.contentItem, 0, 0);
const inset = flickable.height * flickable.fadeAmount + Appearance.padding.large;
const minY = -flickable.topMargin;
const maxY = Math.max(minY, flickable.contentHeight + flickable.bottomMargin - flickable.height);
const target = Math.max(minY, Math.min(pos.y - inset, maxY));
root.animateScroll = true;
flickable.contentY = target;
Qt.callLater(() => root.animateScroll = false);
if (row.flashHighlight !== undefined)
row.flashHighlight();
return true;
}
spacing: Appearance.spacing.large
MouseArea { // Prevent clicks from reaching flickable
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
Component.onCompleted: applySearchAnchor()
Timer {
id: scrollRetry
property real lastHeight: -1
property int stableFrames: 0
property int tries: 0
interval: 16
repeat: true
onTriggered: {
const h = flickable.contentHeight;
if (h === lastHeight && h > flickable.height)
stableFrames++;
else
stableFrames = 0;
lastHeight = h;
const ready = stableFrames >= 3 || tries >= 30;
if (ready) {
if (root.scrollToAnchor(root.sState.searchAnchor))
root.sState.searchAnchor = "";
stop();
}
tries++;
}
}
Connections {
function onHighlightSetting(anchor: string): void {
root.highlightAnchor(anchor);
}
function onSearchAnchorChanged(): void {
root.applySearchAnchor();
}
target: root.sState
}
MouseArea {
Layout.bottomMargin: -flickable.topMargin
implicitHeight: header.implicitHeight - Layout.bottomMargin
implicitWidth: header.implicitWidth
z: 1
@@ -69,6 +159,14 @@ ColumnLayout {
fadeAmount: 0.1
topMargin: Appearance.padding.large
Behavior on contentY {
enabled: root.animateScroll
Anim {
type: Anim.DefaultSpatial
}
}
TapHandler {
onTapped: flickable.focus = true
}
+5
View File
@@ -9,8 +9,13 @@ CustomSwitch {
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string settingAnchor
property string subtext
function flashHighlight(): void {
bg.flashHighlight();
}
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Appearance.padding.largeIncreased
+25
View File
@@ -32,6 +32,31 @@ Item {
implicitHeight: width
radius: Appearance.rounding.large
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: img.status === Image.Ready ? 0 : 1
Behavior on opacity {
Anim {
}
}
sourceComponent: CustomRect {
color: DynamicColors.palette.m3primaryContainer
implicitHeight: loadingIndicator.implicitSize + Appearance.padding.large * 2
implicitWidth: loadingIndicator.implicitSize + Appearance.padding.large * 2
radius: Appearance.rounding.full
LoadingIndicator {
id: loadingIndicator
anchors.centerIn: parent
containsIcon: true
implicitSize: Math.min(imgWrapper.width, imgWrapper.height) * 0.3
}
}
}
Image {
id: img
@@ -267,8 +267,6 @@ Item {
function restoreFromData() {
let data = Wallpapers.getCrop(wrapper.currentScreen.name);
console.log(data.x, data.y);
if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
zoom = data.zoom > 0 ? data.zoom : 1.0;
x = imageX + (data.x * scaledImg.paintedWidth);
+6
View File
@@ -34,6 +34,12 @@ ColumnLayout {
target: root.sState
value: searchField.text.length > 0
}
Binding {
property: "searchText"
target: root.sState
value: searchField.text
}
}
NavLocations {
+236 -3
View File
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Config
import qs.Modules.Settings
@@ -9,7 +10,33 @@ import qs.Modules.Settings
VerticalFadeFlickable {
id: root
readonly property var groups: {
const out = [];
const byPage = ({});
for (const e of results) {
const key = e.pageIdx;
if (byPage[key] === undefined) {
byPage[key] = {
"pageIdx": e.pageIdx,
"page": e.crumbLabels[0],
"icon": e.crumbIcons[0],
"entries": []
};
out.push(byPage[key]);
}
byPage[key].entries.push(e);
}
return out;
}
readonly property var results: {
if (!searching)
return [];
const all = SettingsSearcher.query(search);
return all;
}
required property SettingsState sState
readonly property string search: sState.searchText
readonly property bool searching: search.length > 0
bottomMargin: Appearance.padding.large
contentHeight: content.implicitHeight
@@ -30,14 +57,14 @@ VerticalFadeFlickable {
Repeater {
id: list
model: PageRegistry.pages
model: root.searching ? [] : PageRegistry.pages
CustomRect {
id: item
required property int index
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1]?.category !== modelData.category
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1]?.category !== modelData.category
readonly property bool isCurrentPage: index === root.sState.currentPageIdx
required property var modelData
@@ -120,6 +147,212 @@ VerticalFadeFlickable {
}
}
}
Column {
id: resultList
Layout.fillWidth: true
spacing: Appearance.padding.large
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
move: Transition {
Anim {
properties: "x,y"
}
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
Repeater {
model: ScriptModel {
objectProp: "pageIdx"
values: root.groups
}
ColumnLayout {
id: group
required property int index
required property var modelData
spacing: Appearance.spacing.small
width: resultList.width
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.large
text: group.modelData.icon
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3secondary
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: group.modelData.page
}
}
Column {
id: cardList
Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
move: Transition {
Anim {
properties: "x,y"
}
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
Repeater {
model: ScriptModel {
objectProp: "anchor"
values: group.modelData.entries
}
CustomRect {
id: result
required property int index
readonly property bool isFirst: index === 0
readonly property bool isLast: index === group.modelData.entries.length - 1
required property var modelData
bottomLeftRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: {
const h = resultLayout.implicitHeight + resultLayout.anchors.margins * 2;
return h % 2 === 0 ? h : h + 1;
}
topLeftRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
width: cardList.width
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
ColumnLayout {
id: resultLayout
anchors.fill: parent
anchors.margins: Appearance.padding.large
anchors.rightMargin: result.modelData.togglePath ? toggle.width + Appearance.padding.large * 2 : Appearance.padding.large
spacing: Appearance.spacing.small / 2
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: {
const labels = result.modelData.crumbLabels.slice(1);
const section = result.modelData.section;
const parts = section && section !== labels[labels.length - 1] ? labels.concat(section) : labels;
return parts.join(" \u203a ");
}
visible: text.length > 0
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pointSize: Appearance.font.size.medium
text: SettingsSearcher.highlight(result.modelData.title, root.search, DynamicColors.palette.m3primary)
textFormat: text.includes("<font") ? Text.StyledText : Text.PlainText
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: SettingsSearcher.highlight(result.modelData.subtext, root.search, DynamicColors.palette.m3primary)
textFormat: text.includes("<font") ? Text.StyledText : Text.PlainText
visible: result.modelData.subtext.length > 0
}
}
StateLayer {
id: layer
z: 1
onClicked: {
root.sState.jumpToSetting(result.modelData.pageIdx, result.modelData.subPath, result.modelData.anchor);
}
}
CustomSwitch {
id: toggle
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
cLayer: 3
checked: result.modelData.toggleValue
scale: 0.85
transformOrigin: Item.Right
visible: result.modelData.togglePath
z: 2
onToggled: result.modelData.setToggle(checked)
}
}
}
}
}
}
}
CustomText {
Layout.fillWidth: true
Layout.topMargin: Appearance.padding.large
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
horizontalAlignment: Text.AlignHCenter
text: qsTr("No matching settings")
visible: root.searching && root.results.length === 0
}
}
component RadiusBehavior: Behavior {
+5
View File
@@ -30,6 +30,7 @@ PageBase {
DefaultRow {
first: true
icon: "terminal"
settingAnchor: "apps-default-terminal"
status: Config.general.apps.terminal.join(" ")
text: qsTr("Terminal")
@@ -38,6 +39,7 @@ PageBase {
DefaultRow {
icon: "volume_up"
settingAnchor: "apps-default-audio"
status: Config.general.apps.audio.join(" ")
text: qsTr("Audio")
@@ -46,6 +48,7 @@ PageBase {
DefaultRow {
icon: "play_circle"
settingAnchor: "apps-default-playback"
status: Config.general.apps.playback.join(" ")
text: qsTr("Media playback")
@@ -55,6 +58,7 @@ PageBase {
DefaultRow {
icon: "folder"
last: true
settingAnchor: "apps-default-file-manager"
status: Config.general.apps.explorer.join(" ")
text: qsTr("File manager")
@@ -70,6 +74,7 @@ PageBase {
first: true
icon: "apps"
last: true
settingAnchor: "apps-all-apps"
status: qsTr("Browse installed apps, set favorites and hidden")
text: qsTr("All apps")
+2
View File
@@ -25,6 +25,7 @@ PageBase {
first: true
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
label: qsTr("Output")
settingAnchor: "audio-output"
value: Audio.volume
valueLabel: Math.round(value * 100) + "%"
@@ -55,6 +56,7 @@ PageBase {
first: true
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
label: qsTr("Input")
settingAnchor: "audio-input"
value: Audio.sourceVolume
valueLabel: Math.round(value * 100) + "%"
+11 -1
View File
@@ -48,8 +48,8 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
active: root.clockFormats.find(item => item.value === Config.general.dateFormat)
first: true
last: true
menuItems: root.clockFormats
settingAnchor: "bar-clock-format"
subtext: qsTr("Change how time is displayed in the widget")
text: qsTr("Time format")
@@ -57,5 +57,15 @@ PageBase {
Config.general.dateFormat = item.value;
}
}
ToggleRow {
checked: Config.services.useTwelveHourClock
last: true
settingAnchor: "bar-clock-twelve-hour"
subtext: qsTr("Format timestamps for twelve or twenty-four hours in UI")
text: qsTr("Twelve hour clock")
onToggled: Config.services.useTwelveHourClock = checked
}
}
}
@@ -25,6 +25,7 @@ PageBase {
ToggleRow {
checked: Config.bar.tray.showAudio
first: true
settingAnchor: "bar-status-speakers"
text: qsTr("Speakers")
onToggled: Config.bar.tray.showAudio = checked
@@ -32,6 +33,7 @@ PageBase {
ToggleRow {
checked: Config.bar.tray.showMicrophone
settingAnchor: "bar-status-mic"
text: qsTr("Microphone")
onToggled: Config.bar.tray.showMicrophone = checked
@@ -62,6 +64,7 @@ PageBase {
ToggleRow {
checked: Config.bar.tray.showPower
last: true
settingAnchor: "bar-status-power"
text: qsTr("Battery")
onToggled: Config.bar.tray.showPower = checked
@@ -75,6 +78,7 @@ PageBase {
ToggleRow {
checked: Config.bar.popouts.audio
first: true
settingAnchor: "bar-status-audio-popout"
subtext: qsTr("Show a details popout when hovering the audio icons")
text: qsTr("Audio popout on hover")
@@ -84,6 +88,7 @@ PageBase {
ToggleRow {
checked: Config.bar.popouts.upower
last: true
settingAnchor: "bar-status-power-popout"
subtext: qsTr("Show a details popout when hovering the power icon")
text: qsTr("Power popout on hover")
+20 -1
View File
@@ -19,7 +19,7 @@ PageBase {
SpinRow {
first: true
from: 12
last: true
settingAnchor: "bar-tray-iconsize"
stepSize: 1
text: qsTr("Icon size")
to: 24
@@ -27,5 +27,24 @@ PageBase {
onMoved: value => Config.bar.tray.trayIconSize = value
}
ToggleRow {
checked: Config.bar.tray.showOnHover
settingAnchor: "bar-tray-show-popout-on-hover"
subtext: Config.bar.tray.showOnHover ? qsTr("Will show context menu on hover") : qsTr("Will show context menu on right-click")
text: qsTr("Show popout on hover")
onToggled: Config.bar.tray.showOnHover = checked
}
ToggleRow {
checked: Config.bar.tray.recolorIcons
last: true
settingAnchor: "bar-tray-recolor-icons"
subtext: qsTr("Recolors icons to fit current scheme")
text: qsTr("Recolor icons")
onToggled: Config.bar.tray.recolorIcons = checked
}
}
}
@@ -26,6 +26,7 @@ PageBase {
checked: Config.bar.autoHide
first: true
last: true
settingAnchor: "bar-autohide"
subtext: qsTr("Hide the bar, reveal on hover")
text: qsTr("Auto hide")
@@ -40,6 +41,7 @@ PageBase {
NavRow {
first: true
icon: "widgets"
settingAnchor: "bar-tray"
status: qsTr("System tray icons")
text: qsTr("Tray")
@@ -48,6 +50,7 @@ PageBase {
NavRow {
icon: "signal_cellular_alt"
settingAnchor: "bar-statusicons"
status: qsTr("Visible indicators")
text: qsTr("Status icons")
@@ -57,6 +60,7 @@ PageBase {
NavRow {
icon: "schedule"
last: true
settingAnchor: "bar-clock"
status: qsTr("Date, icon, background")
text: qsTr("Clock")
@@ -28,6 +28,7 @@ PageBase {
checked: Config.dashboard.enabled
first: true
last: true
settingAnchor: "dashboard-enabled"
text: qsTr("Enabled")
onToggled: Config.dashboard.enabled = checked
@@ -28,6 +28,7 @@ PageBase {
checked: Config.launcher.enabled
first: true
last: true
settingAnchor: "launcher-enabled"
text: qsTr("Enabled")
onToggled: Config.launcher.enabled = checked
@@ -41,6 +42,7 @@ PageBase {
SpinRow {
first: true
from: 1
settingAnchor: "launcher-max-items-shown"
stepSize: 1
text: qsTr("Max items shown")
to: 20
@@ -51,6 +53,7 @@ PageBase {
SpinRow {
from: 1
settingAnchor: "launcher-max-wallpapers"
stepSize: 1
text: qsTr("Max wallpapers")
to: 30
@@ -62,6 +65,7 @@ PageBase {
SpinRow {
from: 0
last: true
settingAnchor: "launcher-drag-threshold"
stepSize: 5
subtext: qsTr("Pixels dragged before the launcher opens")
text: qsTr("Drag threshold")
@@ -80,6 +84,7 @@ PageBase {
checked: Config.launcher.enableDangerousActions
first: true
last: true
settingAnchor: "launcher-enable-dangerous-actions"
subtext: qsTr("Allow actions that shut down or log out")
text: qsTr("Enable dangerous actions")
@@ -94,6 +99,7 @@ PageBase {
ToggleRow {
checked: Config.launcher.useFuzzy.apps
first: true
settingAnchor: "launcher-apps"
text: qsTr("Apps")
onToggled: Config.launcher.useFuzzy.apps = checked
@@ -101,20 +107,15 @@ PageBase {
ToggleRow {
checked: Config.launcher.useFuzzy.actions
settingAnchor: "launcher-actions"
text: qsTr("Actions")
onToggled: Config.launcher.useFuzzy.actions = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.schemes
text: qsTr("Schemes")
onToggled: Config.launcher.useFuzzy.schemes = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.variants
settingAnchor: "launcher-variants"
text: qsTr("Variants")
onToggled: Config.launcher.useFuzzy.variants = checked
@@ -123,6 +124,7 @@ PageBase {
ToggleRow {
checked: Config.launcher.useFuzzy.wallpapers
last: true
settingAnchor: "launcher-wallpapers"
text: qsTr("Wallpapers")
onToggled: Config.launcher.useFuzzy.wallpapers = checked
@@ -28,6 +28,7 @@ PageBase {
checked: Config.dashboard.performance.enabled
first: true
last: true
settingAnchor: "resources-enabled"
text: qsTr("Enabled")
onToggled: Config.dashboard.performance.enabled = checked
@@ -41,6 +42,7 @@ PageBase {
ToggleRow {
checked: Config.dashboard.performance.showBattery
first: true
settingAnchor: "resources-battery"
text: qsTr("Battery")
onToggled: Config.dashboard.performance.showBattery = checked
@@ -48,13 +50,23 @@ PageBase {
ToggleRow {
checked: Config.dashboard.performance.showGpu
settingAnchor: "resources-gpu"
text: qsTr("GPU")
onToggled: Config.dashboard.performance.showGpu = checked
}
ToggleRow {
checked: Config.dashboard.performance.showVram
settingAnchor: "resources-vram"
text: qsTr("Video memory")
onToggled: Config.dashboard.performance.showVram = checked
}
ToggleRow {
checked: Config.dashboard.performance.showCpu
settingAnchor: "resources-cpu"
text: qsTr("CPU")
onToggled: Config.dashboard.performance.showCpu = checked
@@ -62,6 +74,7 @@ PageBase {
ToggleRow {
checked: Config.dashboard.performance.showMemory
settingAnchor: "resources-memory"
text: qsTr("Memory")
onToggled: Config.dashboard.performance.showMemory = checked
@@ -69,6 +82,7 @@ PageBase {
ToggleRow {
checked: Config.dashboard.performance.showStorage
settingAnchor: "resources-storage"
text: qsTr("Storage")
onToggled: Config.dashboard.performance.showStorage = checked
@@ -77,6 +91,7 @@ PageBase {
ToggleRow {
checked: Config.dashboard.performance.showNetwork
last: true
settingAnchor: "resources-network"
text: qsTr("Network")
onToggled: Config.dashboard.performance.showNetwork = checked
@@ -26,6 +26,7 @@ PageBase {
ToggleRow {
checked: Config.sidebar.enabled
first: true
settingAnchor: "sidebar-enabled"
text: qsTr("Enabled")
onToggled: Config.sidebar.enabled = checked
@@ -34,6 +35,7 @@ PageBase {
SpinRow {
from: 0
last: true
settingAnchor: "sidebar-drag-threshold"
stepSize: 5
subtext: qsTr("Pixels dragged before the sidebar opens")
text: qsTr("Drag threshold")
+5
View File
@@ -16,6 +16,7 @@ PageBase {
NavRow {
first: true
icon: "dock_to_bottom"
settingAnchor: "panels-bar"
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
text: qsTr("Bar")
@@ -24,6 +25,7 @@ PageBase {
NavRow {
icon: "dashboard"
settingAnchor: "panels-dashboard"
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Dashboard")
@@ -32,6 +34,7 @@ PageBase {
NavRow {
icon: "insert_chart"
settingAnchor: "panels-resources"
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Resources")
@@ -40,6 +43,7 @@ PageBase {
NavRow {
icon: "apps"
settingAnchor: "panels-launcher"
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Launcher")
@@ -49,6 +53,7 @@ PageBase {
NavRow {
icon: "dock_to_right"
last: true
settingAnchor: "panels-sidebar"
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Sidebar")
@@ -28,6 +28,7 @@ PageBase {
ToggleRow {
checked: Config.screenshot.enable_pp
first: true
settingAnchor: "screenshot-enable"
text: qsTr("Enable effects")
onToggled: Config.screenshot.enable_pp = checked
@@ -37,6 +38,7 @@ PageBase {
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp
last: true
settingAnchor: "screenshot-mode"
subtext: qsTr("Automatic or manual effect values")
text: qsTr("Effects mode")
@@ -67,6 +69,7 @@ PageBase {
checked: Config.screenshot.rounding
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
settingAnchor: "screenshot-enable-rounded-corners"
text: qsTr("Enable rounded corners")
onToggled: Config.screenshot.rounding = checked
@@ -76,6 +79,7 @@ PageBase {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
from: 0
last: true
settingAnchor: "screenshot-corner-radius"
stepSize: 1
text: qsTr("Corner radius")
to: 50
@@ -95,6 +99,7 @@ PageBase {
checked: Config.screenshot.shadow
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
settingAnchor: "screenshot-enable-shadow"
text: qsTr("Enable shadow")
onToggled: Config.screenshot.shadow = checked
@@ -103,6 +108,7 @@ PageBase {
SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: 0
settingAnchor: "screenshot-shadow-blur-amount"
stepSize: 1
text: qsTr("Shadow blur amount")
to: 100
@@ -117,6 +123,7 @@ PageBase {
SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100
settingAnchor: "screenshot-shadow-horizontal-offset"
stepSize: 10
text: qsTr("Shadow horizontal offset")
to: 100
@@ -132,6 +139,7 @@ PageBase {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100
last: true
settingAnchor: "screenshot-shadow-vertical-offset"
stepSize: 10
text: qsTr("Shadow vertical offset")
to: 100
@@ -25,6 +25,7 @@ PageBase {
ToggleRow {
checked: Config.notifs.showInFullscreen
first: true
settingAnchor: "notif-show-in-fullscreen"
subtext: qsTr("Whether notifications appear over fullscreen apps")
text: qsTr("Show in fullscreen")
@@ -33,6 +34,7 @@ PageBase {
ToggleRow {
checked: Config.notifs.expire
settingAnchor: "notif-expire-automatically"
subtext: qsTr("Dismiss notifications after their timeout")
text: qsTr("Expire automatically")
@@ -41,6 +43,7 @@ PageBase {
ToggleRow {
checked: Config.notifs.openExpanded
settingAnchor: "notif-open-expanded"
subtext: qsTr("Show notifications expanded by default")
text: qsTr("Open expanded")
@@ -49,6 +52,7 @@ PageBase {
SpinRow {
from: 1000
settingAnchor: "notif-default-timeout"
stepSize: 500
subtext: qsTr("Time before a notification dismisses (ms)")
text: qsTr("Default timeout")
@@ -61,6 +65,7 @@ PageBase {
SpinRow {
from: 1
last: true
settingAnchor: "notif-group-preview-count"
stepSize: 1
subtext: qsTr("Notifications shown per group before collapsing")
text: qsTr("Group preview count")
@@ -78,6 +83,7 @@ PageBase {
SpinRow {
first: true
from: 1
settingAnchor: "notif-visible-toasts"
stepSize: 1
subtext: qsTr("Maximum number of toasts shown at once")
text: qsTr("Visible toasts")
@@ -89,6 +95,7 @@ PageBase {
ToggleRow {
checked: Config.utilities.toasts.chargingChanged
settingAnchor: "notif-charging-changes"
text: qsTr("Charging changes")
onToggled: Config.utilities.toasts.chargingChanged = checked
@@ -96,6 +103,7 @@ PageBase {
ToggleRow {
checked: Config.utilities.toasts.gameModeChanged
settingAnchor: "notif-game-mode-changes"
text: qsTr("Game mode changes")
onToggled: Config.utilities.toasts.gameModeChanged = checked
@@ -110,6 +118,7 @@ PageBase {
ToggleRow {
checked: Config.utilities.toasts.audioOutputChanged
settingAnchor: "notif-audio-output-changes"
text: qsTr("Audio output changes")
onToggled: Config.utilities.toasts.audioOutputChanged = checked
@@ -118,6 +127,7 @@ PageBase {
ToggleRow {
checked: Config.utilities.toasts.audioInputChanged
last: true
settingAnchor: "notif-audio-input-changes"
text: qsTr("Audio input changes")
onToggled: Config.utilities.toasts.audioInputChanged = checked
+11
View File
@@ -71,6 +71,7 @@ PageBase {
first: true
icon: "notifications"
last: true
settingAnchor: "services-notifications"
status: qsTr("Notifications, toasts, timeouts")
text: qsTr("Notifications")
@@ -85,6 +86,7 @@ PageBase {
SpinRow {
first: true
from: 100
settingAnchor: "services-media-refresh"
stepSize: 50
subtext: qsTr("How often the media position updates (ms)")
text: qsTr("Media refresh")
@@ -97,6 +99,7 @@ PageBase {
SpinRow {
from: 0.5
last: true
settingAnchor: "services-system-stats-refresh"
stepSize: 0.5
subtext: qsTr("CPU, memory and GPU update interval (seconds)")
text: qsTr("System stats refresh")
@@ -118,6 +121,7 @@ PageBase {
first: true
last: true
menuItems: playerVariants.instances
settingAnchor: "services-default-player"
subtext: qsTr("Preferred media player when several are open")
text: qsTr("Default player")
@@ -132,6 +136,7 @@ PageBase {
SpinRow {
first: true
from: 1
settingAnchor: "services-volume-step"
stepSize: 1
subtext: qsTr("Amount the volume changes per input (%)")
text: qsTr("Volume step")
@@ -143,6 +148,7 @@ PageBase {
SpinRow {
from: 1
settingAnchor: "services-brightness-step"
stepSize: 1
subtext: qsTr("Amount the brightness changes per input (%)")
text: qsTr("Brightness step")
@@ -154,6 +160,7 @@ PageBase {
SpinRow {
from: 1
settingAnchor: "services-brightness-minimum"
stepSize: 1
subtext: qsTr("Lowest allowed brightness (%)")
text: qsTr("Brightness minimum")
@@ -166,6 +173,7 @@ PageBase {
SpinRow {
from: 50
last: true
settingAnchor: "services-max-volume"
stepSize: 5
subtext: qsTr("Upper limit for output volume (%)")
text: qsTr("Max volume")
@@ -183,6 +191,7 @@ PageBase {
SpinRow {
first: true
from: 10
settingAnchor: "services-visualizer-resolution"
stepSize: 2
subtext: qsTr("Resolution of the audio visualizer")
text: qsTr("Visualizer resolution")
@@ -194,6 +203,7 @@ PageBase {
ToggleRow {
checked: Config.general.color.smart
settingAnchor: "services-smart-color-scheme"
subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme")
@@ -205,6 +215,7 @@ PageBase {
last: true
menuItems: root.gpuItems
menuOnTop: true
settingAnchor: "services-gpu"
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
text: qsTr("GPU")
+5
View File
@@ -49,6 +49,7 @@ PageBase {
ToggleRow {
checked: Config.background.enabled
first: true
settingAnchor: "style-display-wallpaper"
text: qsTr("Display wallpaper")
onToggled: Config.background.enabled = checked
@@ -57,6 +58,7 @@ PageBase {
ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: DynamicColors.transparency.enabled
settingAnchor: "style-transparency"
subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers)
text: qsTr("Transparency")
@@ -67,6 +69,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: !DynamicColors.light
last: true
settingAnchor: "style-dark-theme"
text: qsTr("Dark theme")
onToggled: DynamicColors.setMode(checked ? "dark" : "light")
@@ -88,6 +91,7 @@ PageBase {
checked: Config.general.color.scheduleDark
first: true
settingAnchor: "style-schedule-dark-mode"
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule dark mode")
@@ -129,6 +133,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset
last: true
settingAnchor: "style-schedule-hyprsunset"
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule hyprsunset")
+178
View File
@@ -0,0 +1,178 @@
pragma Singleton
import "../../scripts/fzf.js" as Fzf
import QtQuick
import Quickshell
import ZShell
import qs.Config
Singleton {
id: root
property var fzfFinder: null
readonly property var highlightCache: ({
"search": "",
"pattern": null
})
property var inverted: ({})
property var ranking: ({})
function highlight(text: string, search: string, colour: color): string {
const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
if (search.length === 0)
return escaped;
const cache = root.highlightCache;
if (search !== cache.search) {
const tokens = tokenize(search);
cache.search = search;
if (tokens.length === 0)
cache.pattern = null;
else {
const escapedTokens = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
cache.pattern = new RegExp("\\b(" + escapedTokens.join("|") + ")", "gi");
}
}
const pattern = cache.pattern;
if (!pattern)
return escaped;
pattern.lastIndex = 0;
if (!pattern.test(escaped))
return escaped;
pattern.lastIndex = 0;
return escaped.replace(pattern, `<font color="${colour}">$1</font>`);
}
function lookup(token: string): var {
const result = ({});
const exact = root.inverted[token] !== undefined;
const keys = exact ? [token] : Object.keys(root.inverted).filter(k => k.startsWith(token));
for (const key of keys) {
const rank = root.ranking[key] ?? ({});
for (const id of root.inverted[key]) {
const w = rank[id] ?? 0.1;
if (result[id] === undefined || w > result[id])
result[id] = w;
}
}
return result;
}
function query(search: string): list<QtObject> {
const tokens = root.tokenize(search);
if (tokens.length === 0)
return [];
const scores = ({});
const hitCounts = ({});
for (const token of tokens) {
const matches = root.lookup(token);
for (const id in matches) {
scores[id] = (scores[id] ?? 0) + matches[id];
hitCounts[id] = (hitCounts[id] ?? 0) + 1;
}
}
const ranked = Object.keys(scores).filter(id => hitCounts[id] === tokens.length).sort((a, b) => scores[b] - scores[a] || (parseInt(a) - parseInt(b))).slice(0, 25);
const all = entries.instances;
const out = ranked.map(id => all[parseInt(id)]).filter(e => e !== undefined);
if (out.length < 5 && root.fzfFinder) {
const seen = ({});
for (const id of ranked)
seen[id] = true;
const fuzzy = root.fzfFinder.find(search);
for (const r of fuzzy) {
const idx = r.item.idx;
if (seen[idx])
continue;
seen[idx] = true;
const entry = all[idx];
if (entry !== undefined)
out.push(entry);
if (out.length >= 25)
break;
}
}
return out;
}
function tokenize(text: string): var {
return text.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 0);
}
Component.onCompleted: {
try {
const data = JSON.parse(ZUtils.settingsIndex());
entries.model = data.entries;
root.inverted = data.inverted ?? {};
root.ranking = data.ranking ?? {};
const docs = data.entries.map((e, i) => ({
idx: i,
text: e.title
}));
root.fzfFinder = new Fzf.Finder(docs, {
selector: d => d.text,
limit: 25
});
} catch (e) {
entries.model = [];
root.inverted = {};
root.ranking = {};
root.fzfFinder = null;
}
}
Variants {
id: entries
SettingEntry {
}
}
component SettingEntry: QtObject {
readonly property string anchor: modelData.anchor ?? ""
readonly property var crumbIcons: modelData.crumbIcons
readonly property var crumbLabels: modelData.crumbLabels
readonly property bool isToggle: togglePath.length > 0
required property var modelData
readonly property int pageIdx: modelData.pageIdx
readonly property string section: modelData.section ?? ""
readonly property var subPath: modelData.subPath
readonly property string subtext: modelData.subtext ?? ""
readonly property string title: modelData.title
readonly property string togglePath: modelData.togglePath ?? ""
readonly property bool toggleValue: {
if (!isToggle)
return false;
let obj = Config;
const parts = togglePath.split(".");
for (const part of parts) {
if (obj === undefined || obj === null)
return false;
obj = obj[part];
}
return obj ?? false;
}
function setToggle(value: bool): void {
if (!isToggle)
return;
const parts = togglePath.split(".");
let obj = Config;
for (let k = 0; k < parts.length - 1; k++) {
if (obj === undefined || obj === null)
return;
obj = obj[parts[k]];
}
if (obj !== undefined && obj !== null)
obj[parts[parts.length - 1]] = value;
}
}
}
+34 -1
View File
@@ -8,14 +8,19 @@ QtObject {
property bool animatingContainer
property int currentPageIdx
property bool isWindow
property string lastAnchor
property list<int> pendingSubPath
property ShellScreen screen
property string searchAnchor
property bool searchOpen
property string searchText
property DesktopEntry selectedApp
property BluetoothDevice selectedBtDevice
property string selectedWallpaperCategory
property list<int> subPageIdxStack
signal close
signal highlightSetting(anchor: string)
signal subPageClosed
signal subPageOpened(idx: int)
@@ -24,10 +29,38 @@ QtObject {
subPageIdxStack.pop();
}
function jumpToSetting(pageIdx: int, subPath: var, anchor: string): void {
const samePage = currentPageIdx === pageIdx;
const sameSub = subPageIdxStack.length === subPath.length && subPath.every((v, i) => subPageIdxStack[i] === v);
if (samePage && sameSub && anchor === lastAnchor) {
highlightSetting(anchor);
return;
}
lastAnchor = anchor;
if (samePage && sameSub) {
searchAnchor = "";
searchAnchor = anchor;
return;
}
searchAnchor = anchor;
if (!samePage) {
pendingSubPath = subPath.slice();
currentPageIdx = pageIdx;
} else {
while (subPageIdxStack.length > 0)
closeSubPage();
for (let i = 0; i < subPath.length; i++)
openSubPage(subPath[i]);
}
}
function openSubPage(idx: int): void {
subPageIdxStack.push(idx);
subPageOpened(idx);
}
onCurrentPageIdxChanged: subPageIdxStack.length = 0
onCurrentPageIdxChanged: {
subPageIdxStack = pendingSubPath;
pendingSubPath = [];
}
}
-2
View File
@@ -39,8 +39,6 @@ Item {
sState.animatingContainer: content.opacity < 1
sState.currentPageIdx: ["wallpaper"][0]
sState.screen: root.screen
onClose: console.log("shouldclose")
}
}
}

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