Compare commits
15
Commits
main
..
fda39fc4c0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fda39fc4c0
|
||
|
|
9e97569467
|
||
|
|
630b05ff2c
|
||
|
|
b86f865396
|
||
|
|
34585a639b
|
||
|
|
163370354a
|
||
|
|
012d2777c2
|
||
|
|
9c0b74ff66 | ||
|
|
24825250bb
|
||
|
|
1364b5ed1a
|
||
|
|
ef4d849b4b
|
||
|
|
e12a047da2
|
||
|
|
5db2718b04
|
||
|
|
e50a28b877
|
||
|
|
0e23b18f72
|
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Docker CLI
|
- name: Install Docker CLI
|
||||||
run: apk add --no-cache docker-cli
|
run: apk add --no-cache docker-cli docker-cli-buildx
|
||||||
|
|
||||||
- name: Login to registry
|
- name: Login to registry
|
||||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
|
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
|
||||||
|
|||||||
@@ -20,28 +20,10 @@ jobs:
|
|||||||
-exec clang-format -i --style=file {} +
|
-exec clang-format -i --style=file {} +
|
||||||
git diff --exit-code && echo "clang-format: passed"
|
git diff --exit-code && echo "clang-format: passed"
|
||||||
|
|
||||||
build:
|
build & lint:
|
||||||
runs-on: alpine
|
runs-on: alpine
|
||||||
container:
|
container:
|
||||||
image: git.aramjonghu.dev/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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -58,5 +40,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json
|
sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \
|
run-clang-tidy -p build -j "$(nproc)" \
|
||||||
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter'
|
-source-filter="^$(pwd)/Plugins/" \
|
||||||
|
-header-filter=".*Plugins/.*" \
|
||||||
|
-removed-arg=-mno-direct-extern-access
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fmt:
|
fmt & lint:
|
||||||
runs-on: alpine
|
runs-on: alpine
|
||||||
container: node:26-alpine
|
container: node:26-alpine
|
||||||
|
|
||||||
@@ -24,20 +24,6 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "No JS/TS files found"
|
echo "No JS/TS files found"
|
||||||
fi
|
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
|
- name: ESLint
|
||||||
run: |
|
run: |
|
||||||
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
|
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
static:
|
fmt & lint:
|
||||||
runs-on: debian
|
runs-on: debian
|
||||||
container: node:26-trixie-slim
|
container: node:26-trixie-slim
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
ruff check .
|
ruff check .
|
||||||
|
|
||||||
verify:
|
test & nuitka build:
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: debian
|
runs-on: debian
|
||||||
container: node:26-trixie-slim
|
container: node:26-trixie-slim
|
||||||
|
|||||||
+21
-86
@@ -3,8 +3,9 @@ name: Rust
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
# jobs can be separated if we integrate tests
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build, clippy & fmt:
|
||||||
runs-on: alpine
|
runs-on: alpine
|
||||||
container: node:26-alpine
|
container: node:26-alpine
|
||||||
env:
|
env:
|
||||||
@@ -34,7 +35,9 @@ jobs:
|
|||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
git \
|
git \
|
||||||
cargo \
|
cargo \
|
||||||
rust
|
rust \
|
||||||
|
rust-clippy \
|
||||||
|
rustfmt
|
||||||
|
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: |
|
run: |
|
||||||
@@ -50,90 +53,6 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "No Rust project found"
|
echo "No Rust project found"
|
||||||
fi
|
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
|
- name: Clippy
|
||||||
run: |
|
run: |
|
||||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||||
@@ -150,3 +69,19 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "No Rust project found"
|
echo "No Rust project found"
|
||||||
fi
|
fi
|
||||||
|
- 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
|
||||||
|
|||||||
+22
-10
@@ -1,23 +1,35 @@
|
|||||||
**/__pycache__/
|
|
||||||
./result/
|
./result/
|
||||||
.pyre/
|
.pyre/
|
||||||
.cache/
|
|
||||||
.venv/
|
|
||||||
.qmlls.ini
|
|
||||||
build/
|
|
||||||
compile_commands.json
|
|
||||||
testpython
|
|
||||||
pkg/
|
pkg/
|
||||||
*.tar.*
|
*.tar.*
|
||||||
uv.lock
|
|
||||||
.qtcreator/
|
|
||||||
dist/
|
dist/
|
||||||
**/target/
|
**/target/
|
||||||
**/test-plugins/
|
**/test-plugins/
|
||||||
**/Charts/
|
**/Charts/
|
||||||
network-dev/
|
network-dev/
|
||||||
**/zshell.build/
|
|
||||||
**/zshell.dist/
|
**/zshell.dist/
|
||||||
|
|
||||||
|
# Qml
|
||||||
|
.qtcreator/
|
||||||
|
.qmlls.ini
|
||||||
|
|
||||||
|
# CMake compile artifacts
|
||||||
|
.cache/
|
||||||
|
compile_commands.json
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
uv.lock
|
||||||
|
testpython
|
||||||
|
.venv/
|
||||||
|
**/__pycache__/
|
||||||
|
|
||||||
|
# Nuitka compile
|
||||||
|
**/zshell.pyi
|
||||||
|
**/zshell.cpython-*
|
||||||
|
**/zshell.build/
|
||||||
|
|
||||||
|
# Agents stuff
|
||||||
.opencode/
|
.opencode/
|
||||||
run-agent.sh
|
run-agent.sh
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
interactive: !ShellState.forActive().isDrawing
|
|
||||||
|
|
||||||
rebound: Transition {
|
|
||||||
Anim {
|
|
||||||
properties: "x,y"
|
|
||||||
type: Anim.FastEffects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property KineticWheelScroller _scroller: KineticWheelScroller {
|
|
||||||
id: wheelScroller
|
|
||||||
anchors.fill: parent
|
|
||||||
parent: root
|
|
||||||
z: 12
|
|
||||||
flickable: root
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,654 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
|
|
||||||
// Taken from hyprfm: https://github.com/soyeb-jim285/hyprfm/blob/main/src/qml/views/KineticWheelScroller.qml
|
|
||||||
// Adapted to fit this project where needed.
|
|
||||||
|
|
||||||
// Deliberately not a MouseArea: a MouseArea claims the arrow cursor for its
|
|
||||||
// whole area, which silently killed every cursorShape in the views this
|
|
||||||
// overlays (column resize grips, pointer hands). A WheelHandler on a plain
|
|
||||||
// Item takes wheel events without touching the cursor.
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property var flickable
|
|
||||||
|
|
||||||
property real wheelStep: 56
|
|
||||||
property real mouseWheelMultiplier: 1.0
|
|
||||||
property real touchpadMultiplier: 1.35
|
|
||||||
property real minVelocity: 480
|
|
||||||
property real maxVelocity: 5200
|
|
||||||
property real kineticGain: 1.35
|
|
||||||
property real smoothing: 0.72
|
|
||||||
property real edgeEpsilon: 1.0
|
|
||||||
// Browser-style overscroll: larger visible rubber-band, softer
|
|
||||||
// resistance, and a springy settle on release.
|
|
||||||
property real overshootLimit: 140
|
|
||||||
property real overshootResistance: 0.38
|
|
||||||
property real maxOvershootInput: 520
|
|
||||||
// Bounce magnitude per unit of impact velocity. Keep this deliberately
|
|
||||||
// small: slow edge hits should settle, not fire a visibly faster bounce.
|
|
||||||
property real kineticBounceGain: 0.018
|
|
||||||
property real maxKineticBounceInput: 90
|
|
||||||
// Minimum impact velocity that actually produces a visible bounce.
|
|
||||||
// Very slow landings skip bounce entirely — matches what browsers do
|
|
||||||
// when a fling decays to near-zero before reaching the edge.
|
|
||||||
property real minBounceVelocity: 260
|
|
||||||
|
|
||||||
property real velocity: 0
|
|
||||||
property double lastWheelTimestamp: 0
|
|
||||||
property bool kineticCandidate: false
|
|
||||||
property real overshootInput: 0
|
|
||||||
property real overshootReleaseVelocity: 0
|
|
||||||
property real pendingKineticBounceVelocity: 0
|
|
||||||
// Set by runKineticGlide when the glide will clamp against an edge.
|
|
||||||
// It is the glide velocity at the instant of impact, which drives the
|
|
||||||
// elastic bounce amount and duration — browser-style: slow impact
|
|
||||||
// makes a small gentle bounce; fast impact makes a big springy one.
|
|
||||||
property real pendingImpactVelocity: 0
|
|
||||||
|
|
||||||
// Browser-style exponential-feeling glide replaces the linear
|
|
||||||
// flickDeceleration decay. Tail uses OutQuart — soft landing.
|
|
||||||
property real kineticTau: 0.42
|
|
||||||
|
|
||||||
property real mouseGlideTime: 0.09 // decay time constant (s); higher = longer coast
|
|
||||||
property real mouseAccelWindow: 140 // ms between notches that counts as "continuous"
|
|
||||||
property real mouseAccelStep: 0.15 // extra impulse multiplier per consecutive notch
|
|
||||||
property real mouseMaxAccel: 2.5
|
|
||||||
property real mouseSnap: 0.2 // fraction of each notch applied instantly (0 = fully glided, 1 = no smoothing)
|
|
||||||
|
|
||||||
property real mouseVelocity: 0
|
|
||||||
property int mouseDir: 0
|
|
||||||
property int mouseStreak: 0
|
|
||||||
property double mouseLastTime: 0
|
|
||||||
|
|
||||||
// Escape hatch if auto-detection misjudges a device
|
|
||||||
property bool forceMouseWheel: false
|
|
||||||
|
|
||||||
readonly property real visualOvershoot: overshootInput < 0 ? rubberDistance(-overshootInput) : -rubberDistance(overshootInput)
|
|
||||||
|
|
||||||
signal scrollStarted
|
|
||||||
|
|
||||||
WheelHandler {
|
|
||||||
id: wheelHandler
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
||||||
// `blocking` decides whether the event continues to the Flickable
|
|
||||||
// underneath; handleWheel() flips it per event (the old MouseArea
|
|
||||||
// code toggled wheel.accepted for the same purpose).
|
|
||||||
onWheel: wheel => root.handleWheel(wheel)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMouseWheel(wheel) {
|
|
||||||
if (forceMouseWheel)
|
|
||||||
return true;
|
|
||||||
var ay = wheel.angleDelta.y;
|
|
||||||
if (ay === 0)
|
|
||||||
return false;
|
|
||||||
return Math.abs(ay) % 120 === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function minimumContentY() {
|
|
||||||
if (!flickable)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return flickable.originY;
|
|
||||||
}
|
|
||||||
|
|
||||||
function maximumContentY() {
|
|
||||||
if (!flickable)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return Math.max(minimumContentY(), minimumContentY() + flickable.contentHeight - flickable.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function boundedContentY(value) {
|
|
||||||
return Math.max(minimumContentY(), Math.min(maximumContentY(), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function rubberDistance(distance) {
|
|
||||||
if (distance <= 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var scaled = (distance * overshootResistance) / overshootLimit;
|
|
||||||
return overshootLimit * (1 - 1 / (1 + scaled));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setOvershootInput(value) {
|
|
||||||
overshootInput = Math.max(-maxOvershootInput, Math.min(maxOvershootInput, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasOvershoot() {
|
|
||||||
return Math.abs(overshootInput) > edgeEpsilon;
|
|
||||||
}
|
|
||||||
|
|
||||||
function absorbOvershoot(delta) {
|
|
||||||
if (!hasOvershoot())
|
|
||||||
return delta;
|
|
||||||
|
|
||||||
var sameDirection = (overshootInput < 0 && delta < 0) || (overshootInput > 0 && delta > 0);
|
|
||||||
|
|
||||||
if (sameDirection) {
|
|
||||||
setOvershootInput(overshootInput + delta);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var combined = overshootInput + delta;
|
|
||||||
if ((overshootInput < 0 && combined < 0) || (overshootInput > 0 && combined > 0)) {
|
|
||||||
setOvershootInput(combined);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOvershootInput(0);
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deltaFor(wheel) {
|
|
||||||
var rawDelta;
|
|
||||||
if (isMouseWheel(wheel))
|
|
||||||
rawDelta = (wheel.angleDelta.y / 120.0) * wheelStep * mouseWheelMultiplier;
|
|
||||||
else
|
|
||||||
rawDelta = wheel.pixelDelta.y !== 0 ? wheel.pixelDelta.y * touchpadMultiplier : (wheel.angleDelta.y / 120.0) * wheelStep * mouseWheelMultiplier;
|
|
||||||
|
|
||||||
return wheel.inverted ? rawDelta : -rawDelta;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldUseKinetic(wheel, gap) {
|
|
||||||
return wheel.pixelDelta.y !== 0 || (wheel.phase !== Qt.NoScrollPhase && wheel.angleDelta.y === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollBy(delta) {
|
|
||||||
if (!flickable || Math.abs(delta) < 0.01)
|
|
||||||
return;
|
|
||||||
kineticBounceSequence.stop();
|
|
||||||
bounceBackAnimation.stop();
|
|
||||||
|
|
||||||
var remainingDelta = absorbOvershoot(delta);
|
|
||||||
if (Math.abs(remainingDelta) < 0.01)
|
|
||||||
return;
|
|
||||||
var minY = minimumContentY();
|
|
||||||
var maxY = maximumContentY();
|
|
||||||
var next = flickable.contentY + remainingDelta;
|
|
||||||
|
|
||||||
if (next < minY) {
|
|
||||||
flickable.contentY = minY;
|
|
||||||
setOvershootInput(overshootInput + next - minY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next > maxY) {
|
|
||||||
flickable.contentY = maxY;
|
|
||||||
setOvershootInput(overshootInput + next - maxY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
flickable.contentY = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function remainingDistanceFor(velocityValue) {
|
|
||||||
if (!flickable)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (velocityValue > 0)
|
|
||||||
return Math.max(0, maximumContentY() - flickable.contentY);
|
|
||||||
|
|
||||||
if (velocityValue < 0)
|
|
||||||
return Math.max(0, flickable.contentY - minimumContentY());
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clampedContentVelocity(velocityValue) {
|
|
||||||
if (!flickable || velocityValue === 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var remaining = remainingDistanceFor(velocityValue);
|
|
||||||
if (remaining <= edgeEpsilon)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var maxAllowed = Math.sqrt(2 * flickable.flickDeceleration * remaining);
|
|
||||||
var magnitude = Math.min(Math.abs(velocityValue), maxAllowed);
|
|
||||||
return velocityValue < 0 ? -magnitude : magnitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetState() {
|
|
||||||
velocity = 0;
|
|
||||||
lastWheelTimestamp = 0;
|
|
||||||
kineticCandidate = false;
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
overshootReleaseVelocity = 0;
|
|
||||||
finishTimer.stop();
|
|
||||||
kineticAnim.stop();
|
|
||||||
kineticBounceSequence.stop();
|
|
||||||
bounceBackAnimation.stop();
|
|
||||||
stopMouseGlide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bounceBack() {
|
|
||||||
if (!hasOvershoot()) {
|
|
||||||
setOvershootInput(0);
|
|
||||||
overshootReleaseVelocity = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mag = Math.min(1.0, Math.abs(overshootInput) / maxOvershootInput);
|
|
||||||
var speed = Math.abs(overshootReleaseVelocity);
|
|
||||||
var duration = speed >= minBounceVelocity ? Math.round(Math.max(280, Math.min(620, Math.abs(overshootInput) / speed * 1600))) : Math.round(380 + mag * 260);
|
|
||||||
|
|
||||||
kineticBounceSequence.stop();
|
|
||||||
bounceBackAnimation.stop();
|
|
||||||
bounceBackAnimation.duration = duration;
|
|
||||||
bounceBackAnimation.from = overshootInput;
|
|
||||||
bounceBackAnimation.to = 0;
|
|
||||||
overshootReleaseVelocity = 0;
|
|
||||||
bounceBackAnimation.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerKineticBounce(contentVelocity) {
|
|
||||||
if (!flickable || contentVelocity === 0)
|
|
||||||
return;
|
|
||||||
var atTop = flickable.contentY <= minimumContentY() + edgeEpsilon;
|
|
||||||
var atBottom = flickable.contentY >= maximumContentY() - edgeEpsilon;
|
|
||||||
if ((contentVelocity < 0 && !atTop) || (contentVelocity > 0 && !atBottom))
|
|
||||||
return;
|
|
||||||
var speed = Math.abs(contentVelocity);
|
|
||||||
var effectiveVelocity = contentVelocity;
|
|
||||||
if (speed < minBounceVelocity)
|
|
||||||
effectiveVelocity = (contentVelocity < 0 ? -1 : 1) * Math.max(90, speed * 0.6);
|
|
||||||
|
|
||||||
var target = Math.max(-maxKineticBounceInput, Math.min(maxKineticBounceInput, effectiveVelocity * kineticBounceGain));
|
|
||||||
if (Math.abs(target) < 2.5)
|
|
||||||
target = contentVelocity < 0 ? -2.5 : 2.5;
|
|
||||||
|
|
||||||
var mag = Math.min(1.0, Math.abs(target) / maxKineticBounceInput);
|
|
||||||
var inDur = Math.round(150 + mag * 120);
|
|
||||||
var outDur = Math.round(320 + mag * 300);
|
|
||||||
|
|
||||||
kineticBounceSequence.stop();
|
|
||||||
bounceBackAnimation.stop();
|
|
||||||
overshootReleaseVelocity = 0;
|
|
||||||
overshootInput = 0;
|
|
||||||
kineticBounceIn.from = 0;
|
|
||||||
kineticBounceIn.to = target;
|
|
||||||
kineticBounceIn.duration = inDur;
|
|
||||||
kineticBounceOut.from = target;
|
|
||||||
kineticBounceOut.to = 0;
|
|
||||||
kineticBounceOut.duration = outDur;
|
|
||||||
kineticBounceSequence.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopAndSettle() {
|
|
||||||
kineticAnim.stop();
|
|
||||||
if (flickable && flickable.flicking)
|
|
||||||
flickable.cancelFlick();
|
|
||||||
|
|
||||||
resetState();
|
|
||||||
bounceBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
function runKineticGlide(contentVelocity) {
|
|
||||||
// contentVelocity > 0 means the content should scroll downward
|
|
||||||
// (contentY increases), matching deltaFor()'s sign convention.
|
|
||||||
if (!flickable || contentVelocity === 0) {
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var current = flickable.contentY;
|
|
||||||
var unclampedTarget = current + contentVelocity * kineticTau;
|
|
||||||
var target = boundedContentY(unclampedTarget);
|
|
||||||
var distUnclamped = Math.abs(unclampedTarget - current);
|
|
||||||
var distActual = Math.abs(target - current);
|
|
||||||
if (distActual < 4) {
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the glide is clipped by an edge, compute the velocity at the
|
|
||||||
// moment of impact using the OutQuart velocity profile. That is
|
|
||||||
// the velocity the bounce should feed on — gentle impacts get
|
|
||||||
// gentle bounces, hard impacts get big springy ones.
|
|
||||||
var hitsEdge = distActual + 0.5 < distUnclamped;
|
|
||||||
if (hitsEdge && distUnclamped > 0) {
|
|
||||||
var f = distActual / distUnclamped; // fraction covered
|
|
||||||
// OutQuart: y = 1 - (1-x)^4, so velocity fraction at distance
|
|
||||||
// fraction f equals (1-f)^(3/4) (times the constant peak).
|
|
||||||
var velFraction = Math.pow(Math.max(0, 1 - f), 0.75);
|
|
||||||
pendingImpactVelocity = contentVelocity * velFraction;
|
|
||||||
} else {
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
kineticAnim.stop();
|
|
||||||
kineticAnim.from = current;
|
|
||||||
kineticAnim.to = target;
|
|
||||||
// Duration from distance and velocity; clamped so short flicks
|
|
||||||
// stay snappy and long flicks don't run forever.
|
|
||||||
kineticAnim.duration = Math.round(Math.min(1400, Math.max(180, distActual / Math.abs(contentVelocity) * 1800)));
|
|
||||||
kineticAnim.start();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function atEdgeFor(contentVelocity) {
|
|
||||||
if (!flickable)
|
|
||||||
return false;
|
|
||||||
if (contentVelocity > 0)
|
|
||||||
return flickable.contentY >= maximumContentY() - edgeEpsilon;
|
|
||||||
if (contentVelocity < 0)
|
|
||||||
return flickable.contentY <= minimumContentY() + edgeEpsilon;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishScroll() {
|
|
||||||
if (!flickable)
|
|
||||||
return;
|
|
||||||
finishTimer.stop();
|
|
||||||
|
|
||||||
if (hasOvershoot()) {
|
|
||||||
velocity = 0;
|
|
||||||
lastWheelTimestamp = 0;
|
|
||||||
kineticCandidate = false;
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
kineticAnim.stop();
|
|
||||||
bounceBack();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawContentVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, velocity * kineticGain));
|
|
||||||
var contentVelocity = clampedContentVelocity(rawContentVelocity);
|
|
||||||
var useKinetic = kineticCandidate && Math.abs(rawContentVelocity) >= minVelocity;
|
|
||||||
|
|
||||||
velocity = 0;
|
|
||||||
lastWheelTimestamp = 0;
|
|
||||||
kineticCandidate = false;
|
|
||||||
|
|
||||||
if (useKinetic) {
|
|
||||||
if (atEdgeFor(rawContentVelocity)) {
|
|
||||||
// Already at the edge — fling bounces immediately.
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
triggerKineticBounce(rawContentVelocity);
|
|
||||||
} else if (Math.abs(contentVelocity) >= minVelocity && runKineticGlide(contentVelocity)) {
|
|
||||||
// Glide started — bounce fires on kineticAnim.onFinished
|
|
||||||
// if it lands at an edge.
|
|
||||||
pendingKineticBounceVelocity = contentVelocity;
|
|
||||||
} else {
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
flickable.returnToBounds();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
flickable.returnToBounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleWheel(wheel) {
|
|
||||||
wheelHandler.blocking = true;
|
|
||||||
if (!enabled || !visible || !flickable || !flickable.visible || !flickable.interactive) {
|
|
||||||
wheelHandler.blocking = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wheel.modifiers !== Qt.NoModifier) {
|
|
||||||
wheelHandler.blocking = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maximumContentY() <= minimumContentY()) {
|
|
||||||
wheelHandler.blocking = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var delta = deltaFor(wheel);
|
|
||||||
if (delta === 0) {
|
|
||||||
wheelHandler.blocking = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel any in-flight glide — user input always wins, position
|
|
||||||
// stays where it is, then new delta is applied from there.
|
|
||||||
if (kineticAnim.running) {
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
kineticAnim.stop();
|
|
||||||
}
|
|
||||||
kineticBounceSequence.stop();
|
|
||||||
bounceBackAnimation.stop();
|
|
||||||
if (flickable.flicking) {
|
|
||||||
pendingKineticBounceVelocity = 0;
|
|
||||||
pendingImpactVelocity = 0;
|
|
||||||
flickable.cancelFlick();
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = Date.now();
|
|
||||||
var isTouchpad = !isMouseWheel(wheel) && (wheel.pixelDelta.y !== 0 || (wheel.phase !== Qt.NoScrollPhase && wheel.angleDelta.y === 0));
|
|
||||||
console.log("wheel", wheel.angleDelta.y, wheel.pixelDelta.y, wheel.phase);
|
|
||||||
if (!isTouchpad) {
|
|
||||||
velocity = 0;
|
|
||||||
lastWheelTimestamp = 0;
|
|
||||||
kineticCandidate = false;
|
|
||||||
scrollStarted();
|
|
||||||
handleMouseDelta(delta, now);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Touchpad path (unchanged below), but cancel any mouse glide first
|
|
||||||
stopMouseGlide();
|
|
||||||
|
|
||||||
var gap = lastWheelTimestamp > 0 ? now - lastWheelTimestamp : 16;
|
|
||||||
var dt = Math.max(8, Math.min(24, gap));
|
|
||||||
var instantVelocity = (delta / dt) * 1000;
|
|
||||||
|
|
||||||
kineticCandidate = kineticCandidate || shouldUseKinetic(wheel, gap);
|
|
||||||
scrollStarted();
|
|
||||||
scrollBy(delta);
|
|
||||||
|
|
||||||
if (hasOvershoot()) {
|
|
||||||
overshootReleaseVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, instantVelocity));
|
|
||||||
velocity = 0;
|
|
||||||
kineticCandidate = false;
|
|
||||||
lastWheelTimestamp = now;
|
|
||||||
finishTimer.restart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
velocity = lastWheelTimestamp <= 0 ? instantVelocity : velocity * (1.0 - smoothing) + instantVelocity * smoothing;
|
|
||||||
lastWheelTimestamp = now;
|
|
||||||
finishTimer.restart();
|
|
||||||
|
|
||||||
if (wheel.phase === Qt.ScrollEnd)
|
|
||||||
finishScroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopMouseGlide() {
|
|
||||||
mouseGlide.stop();
|
|
||||||
mouseVelocity = 0;
|
|
||||||
mouseDir = 0;
|
|
||||||
mouseStreak = 0;
|
|
||||||
mouseLastTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseDelta(delta, now) {
|
|
||||||
var dir = delta > 0 ? 1 : -1;
|
|
||||||
|
|
||||||
// Edge or active overshoot: existing rubber-band path
|
|
||||||
if (hasOvershoot() || atEdgeFor(dir)) {
|
|
||||||
stopMouseGlide();
|
|
||||||
scrollBy(delta);
|
|
||||||
overshootReleaseVelocity = 0;
|
|
||||||
finishTimer.restart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var continuous = dir === mouseDir && (now - mouseLastTime) <= mouseAccelWindow;
|
|
||||||
if (!continuous)
|
|
||||||
mouseVelocity = 0; // new gesture or reversal: start clean
|
|
||||||
mouseStreak = continuous ? mouseStreak + 1 : 0;
|
|
||||||
|
|
||||||
// Compute the gap BEFORE overwriting mouseLastTime
|
|
||||||
var gap = mouseLastTime > 0 ? Math.max(12, Math.min(mouseAccelWindow, now - mouseLastTime)) : 0;
|
|
||||||
|
|
||||||
mouseDir = dir;
|
|
||||||
mouseLastTime = now;
|
|
||||||
|
|
||||||
var accel = Math.min(mouseMaxAccel, 1 + mouseStreak * mouseAccelStep);
|
|
||||||
|
|
||||||
// --- velocity update (this is the part that changed) ---
|
|
||||||
|
|
||||||
// Part of the notch lands immediately
|
|
||||||
var instant = delta * mouseSnap;
|
|
||||||
flickable.contentY = boundedContentY(flickable.contentY + instant);
|
|
||||||
|
|
||||||
// The rest becomes glide velocity, stacked onto the current velocity
|
|
||||||
var glide = delta * (1 - mouseSnap) * accel;
|
|
||||||
var v = mouseVelocity + glide / mouseGlideTime;
|
|
||||||
|
|
||||||
// While notches keep arriving, don't fall below the rate they imply
|
|
||||||
if (continuous && gap > 0) {
|
|
||||||
var rate = glide / (gap / 1000);
|
|
||||||
if (Math.abs(rate) > Math.abs(v))
|
|
||||||
v = rate;
|
|
||||||
}
|
|
||||||
mouseVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, v));
|
|
||||||
|
|
||||||
// --- end velocity update ---
|
|
||||||
|
|
||||||
if (!mouseGlide.running)
|
|
||||||
mouseGlide.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function stepMouseGlide(dt) {
|
|
||||||
if (!flickable || flickable.dragging) {
|
|
||||||
stopMouseGlide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt = Math.min(dt, 0.05);
|
|
||||||
var decay = Math.exp(-dt / mouseGlideTime);
|
|
||||||
// Exact integral of v0 * e^(-t/tau) over dt, so it's frame-rate independent
|
|
||||||
var step = mouseVelocity * mouseGlideTime * (1 - decay);
|
|
||||||
var next = flickable.contentY + step;
|
|
||||||
var minY = minimumContentY();
|
|
||||||
var maxY = maximumContentY();
|
|
||||||
|
|
||||||
if (next <= minY || next >= maxY) {
|
|
||||||
var impact = mouseVelocity;
|
|
||||||
flickable.contentY = next <= minY ? minY : maxY;
|
|
||||||
mouseGlide.stop();
|
|
||||||
mouseVelocity = 0;
|
|
||||||
mouseDir = 0;
|
|
||||||
mouseStreak = 0;
|
|
||||||
triggerKineticBounce(impact); // bounce scales with the real impact speed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
flickable.contentY = next;
|
|
||||||
mouseVelocity *= decay;
|
|
||||||
|
|
||||||
if (Math.abs(mouseVelocity) < 40) // was 8
|
|
||||||
stopMouseGlide();
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameAnimation {
|
|
||||||
id: mouseGlide
|
|
||||||
onTriggered: root.stepMouseGlide(frameTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: finishTimer
|
|
||||||
interval: 45
|
|
||||||
repeat: false
|
|
||||||
onTriggered: root.finishScroll()
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: kineticAnim
|
|
||||||
target: root.flickable
|
|
||||||
property: "contentY"
|
|
||||||
easing.type: Easing.OutQuart
|
|
||||||
onFinished: {
|
|
||||||
// Prefer the impact-time velocity (computed in runKineticGlide)
|
|
||||||
// so the bounce matches the residual energy at the moment the
|
|
||||||
// glide hit the edge — slow landings → soft bounce, fast
|
|
||||||
// landings → big springy bounce.
|
|
||||||
var bv = Math.abs(root.pendingImpactVelocity) > root.edgeEpsilon ? root.pendingImpactVelocity : root.pendingKineticBounceVelocity;
|
|
||||||
root.pendingImpactVelocity = 0;
|
|
||||||
root.pendingKineticBounceVelocity = 0;
|
|
||||||
if (Math.abs(bv) > root.edgeEpsilon)
|
|
||||||
root.triggerKineticBounce(bv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: bounceBackAnimation
|
|
||||||
target: root
|
|
||||||
property: "overshootInput"
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
id: kineticBounceSequence
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: kineticBounceIn
|
|
||||||
target: root
|
|
||||||
property: "overshootInput"
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: kineticBounceOut
|
|
||||||
target: root
|
|
||||||
property: "overshootInput"
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Translate {
|
|
||||||
id: overshootTranslate
|
|
||||||
y: root.visualOvershoot
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.flickable
|
|
||||||
|
|
||||||
function onInteractiveChanged() {
|
|
||||||
if (!root.flickable.interactive)
|
|
||||||
root.stopAndSettle();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onContentHeightChanged() {
|
|
||||||
if (!root.flickable.moving) {
|
|
||||||
root.flickable.contentY = root.boundedContentY(root.flickable.contentY);
|
|
||||||
root.bounceBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHeightChanged() {
|
|
||||||
if (!root.flickable.moving) {
|
|
||||||
root.flickable.contentY = root.boundedContentY(root.flickable.contentY);
|
|
||||||
root.bounceBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVisibleChanged() {
|
|
||||||
if (!root.flickable.visible)
|
|
||||||
root.stopAndSettle();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMovementEnded() {
|
|
||||||
if (Math.abs(root.pendingKineticBounceVelocity) > root.edgeEpsilon) {
|
|
||||||
var bounceVelocity = root.pendingKineticBounceVelocity;
|
|
||||||
root.pendingKineticBounceVelocity = 0;
|
|
||||||
root.triggerKineticBounce(bounceVelocity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (!flickable)
|
|
||||||
return;
|
|
||||||
flickable.flickDeceleration = 2800;
|
|
||||||
flickable.maximumFlickVelocity = Math.max(flickable.maximumFlickVelocity, maxVelocity);
|
|
||||||
flickable.contentItem.transform = [overshootTranslate];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+261
-15
@@ -4,11 +4,14 @@ pragma ComponentBehavior: Bound
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Notifications
|
import Quickshell.Services.Notifications
|
||||||
import ZShell.Services
|
import Quickshell.Hyprland
|
||||||
|
import QtQuick
|
||||||
|
import ZShell
|
||||||
import qs.Components
|
import qs.Components
|
||||||
|
import qs.Modules
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Paths
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Paths
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
@@ -16,9 +19,11 @@ Singleton {
|
|||||||
|
|
||||||
readonly property var appCooldownMap: new Map()
|
readonly property var appCooldownMap: new Map()
|
||||||
property alias dnd: props.dnd
|
property alias dnd: props.dnd
|
||||||
property alias list: store.rows
|
property list<Notif> list: []
|
||||||
readonly property list<NotificationRow> notClosed: store.rows.filter(n => !n.closed)
|
property bool loaded
|
||||||
readonly property list<NotificationRow> popups: store.rows.filter(n => n.popup)
|
readonly property list<Notif> notClosed: list.filter(n => !n.closed)
|
||||||
|
readonly property list<Notif> popups: list.filter(n => n.popup)
|
||||||
|
property alias server: server
|
||||||
|
|
||||||
function shouldThrottle(appName: string): bool {
|
function shouldThrottle(appName: string): bool {
|
||||||
if (props.dnd || ShellState.anySidebarOpen())
|
if (props.dnd || ShellState.anySidebarOpen())
|
||||||
@@ -41,6 +46,33 @@ Singleton {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onListChanged: {
|
||||||
|
if (loaded) {
|
||||||
|
saveTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: saveTimer
|
||||||
|
|
||||||
|
interval: 1000
|
||||||
|
|
||||||
|
onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({
|
||||||
|
time: n.time,
|
||||||
|
id: n.id,
|
||||||
|
summary: n.summary,
|
||||||
|
body: n.body,
|
||||||
|
appIcon: n.appIcon,
|
||||||
|
appName: n.appName,
|
||||||
|
image: n.image,
|
||||||
|
expireTimeout: n.expireTimeout,
|
||||||
|
urgency: n.urgency,
|
||||||
|
resident: n.resident,
|
||||||
|
hasActionIcons: n.hasActionIcons,
|
||||||
|
actions: n.actions
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
PersistentProperties {
|
PersistentProperties {
|
||||||
id: props
|
id: props
|
||||||
|
|
||||||
@@ -49,12 +81,6 @@ Singleton {
|
|||||||
reloadableId: "notifs"
|
reloadableId: "notifs"
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationStore {
|
|
||||||
id: store
|
|
||||||
|
|
||||||
path: `${Paths.state}/notifs.sqlite`
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationServer {
|
NotificationServer {
|
||||||
id: server
|
id: server
|
||||||
|
|
||||||
@@ -63,12 +89,40 @@ Singleton {
|
|||||||
bodyImagesSupported: true
|
bodyImagesSupported: true
|
||||||
bodyMarkupSupported: true
|
bodyMarkupSupported: true
|
||||||
imageSupported: true
|
imageSupported: true
|
||||||
keepOnReload: true
|
keepOnReload: false
|
||||||
persistenceSupported: true
|
persistenceSupported: true
|
||||||
|
|
||||||
onNotification: notif => {
|
onNotification: notif => {
|
||||||
notif.tracked = true;
|
notif.tracked = true;
|
||||||
store.insert(notif, root.shouldThrottle(notif.appName));
|
|
||||||
|
const is_popup = root.shouldThrottle(notif.appName);
|
||||||
|
|
||||||
|
const comp = notifComp.createObject(root, {
|
||||||
|
popup: is_popup,
|
||||||
|
notification: notif
|
||||||
|
});
|
||||||
|
root.list = [comp, ...root.list];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: storage
|
||||||
|
|
||||||
|
path: `${Paths.state}/notifs.json`
|
||||||
|
printErrors: false
|
||||||
|
|
||||||
|
onLoadFailed: err => {
|
||||||
|
if (err === FileViewError.FileNotFound) {
|
||||||
|
root.loaded = true;
|
||||||
|
Qt.callLater(() => setText("[]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onLoaded: {
|
||||||
|
const data = JSON.parse(text());
|
||||||
|
for (const notif of data)
|
||||||
|
root.list.push(notifComp.createObject(root, notif));
|
||||||
|
root.list.sort((a, b) => b.time - a.time);
|
||||||
|
root.loaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,12 +130,16 @@ Singleton {
|
|||||||
description: "Clear all notifications"
|
description: "Clear all notifications"
|
||||||
name: "clearnotifs"
|
name: "clearnotifs"
|
||||||
|
|
||||||
onPressed: store.clear()
|
onPressed: {
|
||||||
|
for (const notif of root.list.slice())
|
||||||
|
notif.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function clear(): void {
|
function clear(): void {
|
||||||
store.clear();
|
for (const notif of root.list.slice())
|
||||||
|
notif.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableDnd(): void {
|
function disableDnd(): void {
|
||||||
@@ -102,4 +160,192 @@ Singleton {
|
|||||||
|
|
||||||
target: "notifs"
|
target: "notifs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: notifComp
|
||||||
|
|
||||||
|
Notif {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Notif: QtObject {
|
||||||
|
id: notif
|
||||||
|
|
||||||
|
property list<var> actions
|
||||||
|
property string appIcon
|
||||||
|
property string appName
|
||||||
|
property string body
|
||||||
|
property string cachedImageSource: ""
|
||||||
|
property bool cachingImage: false
|
||||||
|
property bool closed
|
||||||
|
readonly property Connections conn: Connections {
|
||||||
|
function onActionsChanged(): void {
|
||||||
|
notif.actions = notif.notification.actions.map(a => ({
|
||||||
|
identifier: a.identifier,
|
||||||
|
text: a.text,
|
||||||
|
invoke: () => a.invoke()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAppIconChanged(): void {
|
||||||
|
notif.appIcon = notif.notification.appIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAppNameChanged(): void {
|
||||||
|
notif.appName = notif.notification.appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBodyChanged(): void {
|
||||||
|
notif.body = notif.notification.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClosed(): void {
|
||||||
|
notif.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExpireTimeoutChanged(): void {
|
||||||
|
notif.expireTimeout = notif.notification.expireTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHasActionIconsChanged(): void {
|
||||||
|
notif.hasActionIcons = notif.notification.hasActionIcons;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHintsChanged(): void {
|
||||||
|
notif.hints = notif.notification.hints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onImageChanged(): void {
|
||||||
|
notif.imageSource = notif.notification.image || "";
|
||||||
|
notif.image = notif.imageSource;
|
||||||
|
notif.cacheImageIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResidentChanged(): void {
|
||||||
|
notif.resident = notif.notification.resident;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSummaryChanged(): void {
|
||||||
|
notif.summary = notif.notification.summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUrgencyChanged(): void {
|
||||||
|
notif.urgency = notif.notification.urgency;
|
||||||
|
}
|
||||||
|
|
||||||
|
target: notif.notification
|
||||||
|
}
|
||||||
|
property real expireTimeout: 5
|
||||||
|
property bool hasActionIcons
|
||||||
|
property var hints
|
||||||
|
property string image
|
||||||
|
property string imageSource
|
||||||
|
property var locks: new Set()
|
||||||
|
property string notifId
|
||||||
|
property Notification notification
|
||||||
|
property bool popup
|
||||||
|
property bool resident
|
||||||
|
property string summary
|
||||||
|
property date time: new Date()
|
||||||
|
readonly property string timeStr: {
|
||||||
|
const diff = Time.date.getTime() - time.getTime();
|
||||||
|
const m = Math.floor(diff / 60000);
|
||||||
|
|
||||||
|
if (m < 1)
|
||||||
|
return qsTr("now");
|
||||||
|
|
||||||
|
const h = Math.floor(m / 60);
|
||||||
|
const d = Math.floor(h / 24);
|
||||||
|
|
||||||
|
if (d > 0)
|
||||||
|
return `${d}d`;
|
||||||
|
if (h > 0)
|
||||||
|
return `${h}h`;
|
||||||
|
return `${m}m`;
|
||||||
|
}
|
||||||
|
readonly property Timer timer: Timer {
|
||||||
|
property bool paused: false
|
||||||
|
property int remainingTime: totalTime
|
||||||
|
property int totalTime: Config.notifs.defaultExpireTimeout
|
||||||
|
|
||||||
|
interval: 50
|
||||||
|
repeat: true
|
||||||
|
running: !paused
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
remainingTime -= interval;
|
||||||
|
|
||||||
|
if (remainingTime <= 0) {
|
||||||
|
remainingTime = 0;
|
||||||
|
notif.popup = false;
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property int urgency: NotificationUrgency.Normal
|
||||||
|
|
||||||
|
function cacheImageIfNeeded(): void {
|
||||||
|
const source = imageSource;
|
||||||
|
|
||||||
|
if (!source || cachingImage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cachedImageSource === source)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cachingImage = true;
|
||||||
|
|
||||||
|
ZShellIo.cacheImage(Qt.resolvedUrl(source), Paths.notifimagecache, (path, url) => {
|
||||||
|
cachedImageSource = source;
|
||||||
|
image = path;
|
||||||
|
cachingImage = false;
|
||||||
|
}, () => {
|
||||||
|
cachingImage = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
closed = true;
|
||||||
|
if (locks.size === 0 && root.list.includes(this)) {
|
||||||
|
root.list = root.list.filter(n => n !== this);
|
||||||
|
notification?.dismiss();
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lock(item: Item): void {
|
||||||
|
locks.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlock(item: Item): void {
|
||||||
|
locks.delete(item);
|
||||||
|
if (closed)
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (!notification)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notifId = notification.id;
|
||||||
|
summary = notification.summary;
|
||||||
|
body = notification.body;
|
||||||
|
appIcon = notification.appIcon;
|
||||||
|
appName = notification.appName;
|
||||||
|
imageSource = notification.image || "";
|
||||||
|
image = imageSource;
|
||||||
|
hints = notification.hints;
|
||||||
|
expireTimeout = notification.expireTimeout;
|
||||||
|
urgency = notification.urgency;
|
||||||
|
resident = notification.resident;
|
||||||
|
hasActionIcons = notification.hasActionIcons;
|
||||||
|
actions = notification.actions.map(a => ({
|
||||||
|
identifier: a.identifier,
|
||||||
|
text: a.text,
|
||||||
|
invoke: () => a.invoke()
|
||||||
|
}));
|
||||||
|
|
||||||
|
cacheImageIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import Quickshell.Widgets
|
|||||||
import Quickshell.Services.Notifications
|
import Quickshell.Services.Notifications
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ZShell.Services
|
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
@@ -278,7 +277,7 @@ CustomRect {
|
|||||||
component NotifLine: CustomText {
|
component NotifLine: CustomText {
|
||||||
id: notifLine
|
id: notifLine
|
||||||
|
|
||||||
required property NotificationRow modelData
|
required property NotifServer.Notif modelData
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: root.urgency === "critical" ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onSurface
|
color: root.urgency === "critical" ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onSurface
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import Quickshell.Widgets
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import ZShell.Components
|
import ZShell.Components
|
||||||
import ZShell.Services
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Daemons
|
import qs.Daemons
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
@@ -98,7 +97,7 @@ Item {
|
|||||||
|
|
||||||
property int idx
|
property int idx
|
||||||
required property int index
|
required property int index
|
||||||
required property NotificationRow modelData
|
required property NotifServer.Notif modelData
|
||||||
readonly property alias nonAnimHeight: notif.nonAnimHeight
|
readonly property alias nonAnimHeight: notif.nonAnimHeight
|
||||||
|
|
||||||
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Tokens.spacing.small)
|
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Tokens.spacing.small)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import QtQuick.Shapes
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Notifications
|
import Quickshell.Services.Notifications
|
||||||
import ZShell.Components
|
import ZShell.Components
|
||||||
import ZShell.Services
|
import qs.Daemons
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import qs.Components
|
import qs.Components
|
||||||
@@ -17,7 +17,7 @@ CustomRect {
|
|||||||
property bool expanded: Config.notifs.openExpanded
|
property bool expanded: Config.notifs.openExpanded
|
||||||
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
||||||
readonly property bool hasImage: modelData.image.length > 0
|
readonly property bool hasImage: modelData.image.length > 0
|
||||||
required property NotificationRow modelData
|
required property NotifServer.Notif modelData
|
||||||
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? Tokens.spacing.extraSmall * 2 + appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
|
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? Tokens.spacing.extraSmall * 2 + appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
|
||||||
|
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondaryContainer : Colors.tPalette.m3surfaceContainer
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondaryContainer : Colors.tPalette.m3surfaceContainer
|
||||||
@@ -56,10 +56,10 @@ CustomRect {
|
|||||||
if (actions.length === 1)
|
if (actions.length === 1)
|
||||||
actions[0].invoke();
|
actions[0].invoke();
|
||||||
}
|
}
|
||||||
onEntered: root.modelData.pausePopup()
|
onEntered: root.modelData.timer.stop()
|
||||||
onExited: {
|
onExited: {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
root.modelData.resumePopup();
|
root.modelData.timer.start();
|
||||||
}
|
}
|
||||||
onPositionChanged: event => {
|
onPositionChanged: event => {
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
@@ -69,14 +69,14 @@ CustomRect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onPressed: event => {
|
onPressed: event => {
|
||||||
root.modelData.pausePopup();
|
root.modelData.timer.stop();
|
||||||
startY = event.y;
|
startY = event.y;
|
||||||
if (event.button === Qt.MiddleButton)
|
if (event.button === Qt.MiddleButton)
|
||||||
root.modelData.close();
|
root.modelData.close();
|
||||||
}
|
}
|
||||||
onReleased: event => {
|
onReleased: event => {
|
||||||
if (!containsMouse)
|
if (!containsMouse)
|
||||||
root.modelData.resumePopup();
|
root.modelData.timer.start();
|
||||||
|
|
||||||
if (Math.abs(root.x) < root.implicitWidth * Config.notifs.clearThreshold)
|
if (Math.abs(root.x) < root.implicitWidth * Config.notifs.clearThreshold)
|
||||||
root.x = 0;
|
root.x = 0;
|
||||||
@@ -200,7 +200,7 @@ CustomRect {
|
|||||||
radiusX: progressIndicator.width / 2 - Tokens.padding.extraSmall / 2
|
radiusX: progressIndicator.width / 2 - Tokens.padding.extraSmall / 2
|
||||||
radiusY: progressIndicator.height / 2 - Tokens.padding.extraSmall / 2
|
radiusY: progressIndicator.height / 2 - Tokens.padding.extraSmall / 2
|
||||||
startAngle: -90
|
startAngle: -90
|
||||||
sweepAngle: (root.modelData.progress / 100) * 360
|
sweepAngle: ((root.modelData.hints.value ?? 0) / 100) * 360
|
||||||
|
|
||||||
Behavior on sweepAngle {
|
Behavior on sweepAngle {
|
||||||
Anim {
|
Anim {
|
||||||
@@ -477,12 +477,7 @@ CustomRect {
|
|||||||
shapeMorph: true
|
shapeMorph: true
|
||||||
text: modelData.text
|
text: modelData.text
|
||||||
|
|
||||||
onClicked: {
|
onClicked: modelData.invoke()
|
||||||
if (modelData.invokable)
|
|
||||||
modelData.invoke();
|
|
||||||
else if (!root.modelData.resident)
|
|
||||||
root.modelData.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import QtQuick
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import ZShell.Components
|
|
||||||
import ZShell.Services
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Daemons
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property CustomText body: (expandedContent.item as ExpandedBody)?.body ?? null
|
readonly property CustomText body: (expandedContent.item as ExpandedBody)?.body ?? null
|
||||||
required property bool expanded
|
required property bool expanded
|
||||||
required property NotificationRow modelData
|
required property NotifServer.Notif modelData
|
||||||
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height
|
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height
|
||||||
required property ScreenState screenState
|
required property ScreenState screenState
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import QtQuick.Layouts
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import ZShell.Services
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Effects
|
import qs.Effects
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Daemons
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property NotificationRow notif
|
required property NotifServer.Notif notif
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: flickable.contentHeight
|
implicitHeight: flickable.contentHeight
|
||||||
@@ -36,7 +36,7 @@ Item {
|
|||||||
{
|
{
|
||||||
isClose: true
|
isClose: true
|
||||||
},
|
},
|
||||||
...Array.from(root.notif?.actions ?? []),
|
...(root.notif?.actions ?? []),
|
||||||
{
|
{
|
||||||
isCopy: true
|
isCopy: true
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ Item {
|
|||||||
Quickshell.clipboardText = root.notif.body;
|
Quickshell.clipboardText = root.notif.body;
|
||||||
actionInner.item.text = "inventory";
|
actionInner.item.text = "inventory";
|
||||||
copyTimer.start();
|
copyTimer.start();
|
||||||
} else if (action.modelData.invokable) {
|
} else if (action.modelData.invoke) {
|
||||||
action.modelData.invoke();
|
action.modelData.invoke();
|
||||||
} else if (!root.notif.resident) {
|
} else if (!root.notif.resident) {
|
||||||
root.notif.close();
|
root.notif.close();
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import qs.Components
|
||||||
|
import ZShell.Config
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Daemons
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ZShell.Components
|
|
||||||
import ZShell.Config
|
|
||||||
import qs.Components
|
|
||||||
import qs.Modules
|
|
||||||
import qs.Daemons
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -93,14 +92,13 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KineticFlickable {
|
CustomFlickable {
|
||||||
id: view
|
id: view
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentHeight: notifList.implicitHeight
|
contentHeight: notifList.implicitHeight
|
||||||
contentWidth: width
|
contentWidth: width
|
||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
clip: true
|
|
||||||
|
|
||||||
CustomScrollBar.vertical: CustomScrollBar {
|
CustomScrollBar.vertical: CustomScrollBar {
|
||||||
flickable: view
|
flickable: view
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import ZShell.Components
|
import ZShell.Components
|
||||||
import ZShell.Services
|
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
import qs.Daemons
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ LazyListView {
|
|||||||
id: notif
|
id: notif
|
||||||
|
|
||||||
required property int index
|
required property int index
|
||||||
required property NotificationRow modelData
|
required property NotifServer.Notif modelData
|
||||||
property int startY
|
property int startY
|
||||||
|
|
||||||
LazyListView.preferredHeight: modelData?.closed || LazyListView.removing ? 0 : notifInner.nonAnimHeight
|
LazyListView.preferredHeight: modelData?.closed || LazyListView.removing ? 0 : notifInner.nonAnimHeight
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ void BlobInvertedRect::outerCornerRadii(float out[4]) const {
|
|||||||
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
||||||
out[0] = std::min(
|
out[0] = std::min(
|
||||||
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
||||||
: base,
|
: base,
|
||||||
maxR);
|
maxR);
|
||||||
out[1] = std::min(
|
out[1] = std::min(
|
||||||
m_outerBottomRightRadius >= 0
|
m_outerBottomRightRadius >= 0
|
||||||
|
|||||||
@@ -60,11 +60,15 @@ class BlobInvertedRect : public BlobShape {
|
|||||||
void setOuterRadius(qreal v);
|
void setOuterRadius(qreal v);
|
||||||
|
|
||||||
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
|
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
|
||||||
[[nodiscard]] qreal outerTopLeftRadius() const { return m_outerTopLeftRadius; }
|
[[nodiscard]] qreal outerTopLeftRadius() const {
|
||||||
|
return m_outerTopLeftRadius;
|
||||||
|
}
|
||||||
|
|
||||||
void setOuterTopLeftRadius(qreal v);
|
void setOuterTopLeftRadius(qreal v);
|
||||||
|
|
||||||
[[nodiscard]] qreal outerTopRightRadius() const { return m_outerTopRightRadius; }
|
[[nodiscard]] qreal outerTopRightRadius() const {
|
||||||
|
return m_outerTopRightRadius;
|
||||||
|
}
|
||||||
|
|
||||||
void setOuterTopRightRadius(qreal v);
|
void setOuterTopRightRadius(qreal v);
|
||||||
|
|
||||||
|
|||||||
@@ -240,8 +240,7 @@ void BlobShape::updatePolish() {
|
|||||||
m_cachedInvertedRadius = 0;
|
m_cachedInvertedRadius = 0;
|
||||||
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
|
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
|
||||||
memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner));
|
memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner));
|
||||||
memset(
|
memset(m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||||
m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
|
||||||
|
|
||||||
auto* inv = m_group->invertedRect();
|
auto* inv = m_group->invertedRect();
|
||||||
if (inv) {
|
if (inv) {
|
||||||
|
|||||||
@@ -23,14 +23,11 @@ qml_module(ZShell-services
|
|||||||
lyriccandidate.hpp lyriccandidate.cpp
|
lyriccandidate.hpp lyriccandidate.cpp
|
||||||
lyrics.hpp lyrics.cpp
|
lyrics.hpp lyrics.cpp
|
||||||
dropimporter.hpp dropimporter.cpp
|
dropimporter.hpp dropimporter.cpp
|
||||||
notificationstore.hpp notificationstore.cpp
|
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Qml
|
Qt6::Qml
|
||||||
Qt6::Quick
|
|
||||||
Qt6::Concurrent
|
Qt6::Concurrent
|
||||||
Qt6::Network
|
Qt6::Network
|
||||||
Qt6::Sql
|
|
||||||
PkgConfig::Pipewire
|
PkgConfig::Pipewire
|
||||||
PkgConfig::Aubio
|
PkgConfig::Aubio
|
||||||
PkgConfig::Cava
|
PkgConfig::Cava
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,248 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QJSValue>
|
|
||||||
#include <QList>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPointer>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <QQmlListProperty>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
namespace ZShell::config {
|
|
||||||
class Notifs;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ZShell::Services {
|
|
||||||
|
|
||||||
class NotificationStore;
|
|
||||||
class NotifImageProvider;
|
|
||||||
|
|
||||||
class NotificationActionRow : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE(
|
|
||||||
"NotificationActionRow instances come from a NotificationStore")
|
|
||||||
|
|
||||||
Q_PROPERTY(bool invokable READ invokable NOTIFY invokableChanged)
|
|
||||||
Q_PROPERTY(QString identifier READ identifier CONSTANT)
|
|
||||||
Q_PROPERTY(QString text READ text CONSTANT)
|
|
||||||
|
|
||||||
public:
|
|
||||||
NotificationActionRow(
|
|
||||||
QObject* action,
|
|
||||||
const QString& identifier,
|
|
||||||
const QString& text,
|
|
||||||
QObject* parent = nullptr);
|
|
||||||
|
|
||||||
[[nodiscard]] bool invokable() const;
|
|
||||||
[[nodiscard]] QString identifier() const;
|
|
||||||
[[nodiscard]] QString text() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE void invoke();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void invokableChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QPointer<QObject> m_action;
|
|
||||||
QString m_identifier;
|
|
||||||
QString m_text;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NotificationRow : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE("NotificationRow instances come from a NotificationStore")
|
|
||||||
|
|
||||||
Q_PROPERTY(QQmlListProperty<NotificationActionRow> actions READ actions NOTIFY actionsChanged)
|
|
||||||
Q_PROPERTY(QString appIcon READ appIcon NOTIFY appIconChanged)
|
|
||||||
Q_PROPERTY(QString appName READ appName NOTIFY appNameChanged)
|
|
||||||
Q_PROPERTY(QString body READ body NOTIFY bodyChanged)
|
|
||||||
Q_PROPERTY(bool closed READ closed NOTIFY closedChanged)
|
|
||||||
Q_PROPERTY(bool hasActionIcons READ hasActionIcons NOTIFY hasActionIconsChanged)
|
|
||||||
Q_PROPERTY(QString image READ image NOTIFY imageChanged)
|
|
||||||
Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged)
|
|
||||||
Q_PROPERTY(int notificationId READ notificationId CONSTANT)
|
|
||||||
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
|
|
||||||
Q_PROPERTY(bool popup READ popup WRITE setPopup NOTIFY popupChanged)
|
|
||||||
Q_PROPERTY(bool resident READ resident NOTIFY residentChanged)
|
|
||||||
Q_PROPERTY(QString summary READ summary NOTIFY summaryChanged)
|
|
||||||
Q_PROPERTY(QDateTime time READ time CONSTANT)
|
|
||||||
Q_PROPERTY(QString timeStr READ timeStr NOTIFY timeStrChanged)
|
|
||||||
Q_PROPERTY(int urgency READ urgency NOTIFY urgencyChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit NotificationRow(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<NotificationActionRow> actions();
|
|
||||||
[[nodiscard]] QString appIcon() const;
|
|
||||||
[[nodiscard]] QString appName() const;
|
|
||||||
[[nodiscard]] QString body() const;
|
|
||||||
[[nodiscard]] bool closed() const;
|
|
||||||
[[nodiscard]] bool hasActionIcons() const;
|
|
||||||
[[nodiscard]] QString image() const;
|
|
||||||
[[nodiscard]] QString imageSource() const;
|
|
||||||
[[nodiscard]] int notificationId() const;
|
|
||||||
[[nodiscard]] int progress() const;
|
|
||||||
[[nodiscard]] bool popup() const;
|
|
||||||
void setPopup(bool popup);
|
|
||||||
[[nodiscard]] bool resident() const;
|
|
||||||
[[nodiscard]] QString summary() const;
|
|
||||||
[[nodiscard]] QDateTime time() const;
|
|
||||||
[[nodiscard]] QString timeStr() const;
|
|
||||||
[[nodiscard]] int urgency() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE void close();
|
|
||||||
Q_INVOKABLE void lock(QObject* item);
|
|
||||||
Q_INVOKABLE void pausePopup();
|
|
||||||
Q_INVOKABLE void resumePopup();
|
|
||||||
Q_INVOKABLE void unlock(QObject* item);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void actionsChanged();
|
|
||||||
void appIconChanged();
|
|
||||||
void appNameChanged();
|
|
||||||
void bodyChanged();
|
|
||||||
void closedChanged();
|
|
||||||
void hasActionIconsChanged();
|
|
||||||
void imageChanged();
|
|
||||||
void imageSourceChanged();
|
|
||||||
void popupChanged();
|
|
||||||
void progressChanged();
|
|
||||||
void residentChanged();
|
|
||||||
void summaryChanged();
|
|
||||||
void timeStrChanged();
|
|
||||||
void urgencyChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend NotificationStore;
|
|
||||||
|
|
||||||
[[nodiscard]] bool expired(qint64 now) const;
|
|
||||||
void refreshTimeStr();
|
|
||||||
|
|
||||||
QList<NotificationActionRow*> m_actions;
|
|
||||||
NotificationStore* m_store = nullptr;
|
|
||||||
|
|
||||||
QString m_appIcon;
|
|
||||||
QString m_appName;
|
|
||||||
QString m_body;
|
|
||||||
QString m_digest;
|
|
||||||
QString m_image;
|
|
||||||
QString m_imageSource;
|
|
||||||
QString m_summary;
|
|
||||||
QString m_timeStr;
|
|
||||||
|
|
||||||
qint64 m_deadline = 0;
|
|
||||||
qint64 m_rowId = -1;
|
|
||||||
qint64 m_timeMs = 0;
|
|
||||||
|
|
||||||
QPointer<QObject> m_notification;
|
|
||||||
QSet<QObject*> m_locks;
|
|
||||||
QJSValue m_jsNotification;
|
|
||||||
|
|
||||||
bool m_closed = false;
|
|
||||||
bool m_hasActionIcons = false;
|
|
||||||
bool m_liveConnected = false;
|
|
||||||
bool m_popup = false;
|
|
||||||
bool m_popupPaused = false;
|
|
||||||
bool m_resident = false;
|
|
||||||
|
|
||||||
int m_notificationId = 0;
|
|
||||||
int m_progress = 0;
|
|
||||||
int m_urgency = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NotificationStore : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(bool autoExpire READ autoExpire WRITE setAutoExpire NOTIFY autoExpireChanged)
|
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
|
||||||
Q_PROPERTY(int popupTimeout READ popupTimeout WRITE setPopupTimeout NOTIFY
|
|
||||||
popupTimeoutChanged)
|
|
||||||
Q_PROPERTY(QQmlListProperty<NotificationRow> rows READ rows NOTIFY rowsChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit NotificationStore(QObject* parent = nullptr);
|
|
||||||
~NotificationStore() override;
|
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
|
||||||
void setPath(const QString& path);
|
|
||||||
|
|
||||||
[[nodiscard]] bool autoExpire() const;
|
|
||||||
void setAutoExpire(bool autoExpire);
|
|
||||||
|
|
||||||
[[nodiscard]] int popupTimeout() const;
|
|
||||||
void setPopupTimeout(int timeout);
|
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<NotificationRow> rows();
|
|
||||||
|
|
||||||
Q_INVOKABLE NotificationRow* insert(const QJSValue& notification, bool popup);
|
|
||||||
Q_INVOKABLE void clear();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void autoExpireChanged();
|
|
||||||
void pathChanged();
|
|
||||||
void popupTimeoutChanged();
|
|
||||||
void rowsChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void handleLiveUpdate();
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend NotificationRow;
|
|
||||||
|
|
||||||
void attachNotification(
|
|
||||||
NotificationRow* row, QObject* notification, const QJSValue& js);
|
|
||||||
void applyNotifConfig(const config::Notifs* notifs);
|
|
||||||
void closeDb();
|
|
||||||
void copyFields(NotificationRow* row, QObject* notification);
|
|
||||||
void handleClosed(NotificationRow* row);
|
|
||||||
void insertDbRow(NotificationRow* row);
|
|
||||||
void loadRows();
|
|
||||||
void migrateLegacyJson();
|
|
||||||
[[nodiscard]] bool openDb();
|
|
||||||
void orphanCleanup();
|
|
||||||
void persistPopup(const NotificationRow* row);
|
|
||||||
void prune();
|
|
||||||
void registerProvider();
|
|
||||||
void removeRow(NotificationRow* row);
|
|
||||||
// Coalesced rowsChanged: a burst of inserts/removes triggers one QML
|
|
||||||
// rebind on the next event loop turn instead of one per notification.
|
|
||||||
void scheduleRowsChanged();
|
|
||||||
[[nodiscard]] NotificationRow* rowForNotificationId(int id) const;
|
|
||||||
void startImageCache(NotificationRow* row);
|
|
||||||
void storeImageBlob(
|
|
||||||
const QPointer<NotificationRow>& row,
|
|
||||||
const QString& digest,
|
|
||||||
const QByteArray& png);
|
|
||||||
void tick();
|
|
||||||
void trimRows();
|
|
||||||
void updateDbRow(NotificationRow* row);
|
|
||||||
|
|
||||||
static QList<NotificationActionRow*> parseActions(
|
|
||||||
const QJSValue& actions, NotificationRow* row);
|
|
||||||
|
|
||||||
int m_popupTimeout = 5000;
|
|
||||||
|
|
||||||
qint64 m_nextPrune = 0;
|
|
||||||
qint64 m_nextTimeStrRefresh = 0;
|
|
||||||
bool m_autoExpire = true;
|
|
||||||
bool m_clearing = false;
|
|
||||||
bool m_rowsSignalPending = false;
|
|
||||||
|
|
||||||
QString m_uuid;
|
|
||||||
QString m_path;
|
|
||||||
|
|
||||||
QList<NotificationRow*> m_rows;
|
|
||||||
QHash<QObject*, NotificationRow*> m_liveRows;
|
|
||||||
|
|
||||||
NotifImageProvider* m_provider = nullptr;
|
|
||||||
QTimer* m_tick = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ZShell::Services
|
|
||||||
+25
-53
@@ -1,56 +1,28 @@
|
|||||||
FROM archlinux:latest
|
FROM archlinux:latest
|
||||||
|
|
||||||
RUN pacman -Syyu --noconfirm
|
RUN pacman -Syu --noconfirm \
|
||||||
|
&& pacman -S --needed --noconfirm \
|
||||||
|
base-devel cmake ninja clang lld nodejs tree-sitter \
|
||||||
|
jkqtplotter cmark-gfm git wayland-protocols ffmpeg mpv \
|
||||||
|
qt6-base qt6-declarative qt6-shadertools qt6-5compat \
|
||||||
|
qt6-canvaspainter qt6-wayland qt5-wayland \
|
||||||
|
libqalculate pipewire aubio cava lm_sensors glib2 \
|
||||||
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
# Build toolchain
|
RUN pacman -Syu --noconfirm \
|
||||||
RUN pacman -S --noconfirm \
|
&& pacman -S --needed --noconfirm \
|
||||||
base-devel \
|
sudo meson autoconf-archive python-tqdm \
|
||||||
cmake \
|
&& useradd -m builder \
|
||||||
ninja \
|
&& printf '%s\n' 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder \
|
||||||
clang \
|
&& chmod 0440 /etc/sudoers.d/builder \
|
||||||
lld \
|
&& for i in 1 2 3 4 5; do \
|
||||||
nodejs \
|
rm -rf /tmp/libcava; \
|
||||||
tree-sitter \
|
if git clone --depth 1 https://aur.archlinux.org/libcava.git /tmp/libcava; then break; fi; \
|
||||||
jkqtplotter \
|
sleep 5; \
|
||||||
cmark-gfm \
|
done \
|
||||||
git
|
&& test -d /tmp/libcava/.git \
|
||||||
|
&& chown -R builder:builder /tmp/libcava \
|
||||||
# Qt6
|
&& su builder -c "cd /tmp/libcava && makepkg -si --noconfirm" \
|
||||||
RUN pacman -S --noconfirm \
|
&& rm -rf /tmp/libcava /etc/sudoers.d/builder \
|
||||||
qt6-base \
|
&& userdel -r builder \
|
||||||
qt6-declarative \
|
&& pacman -Scc --noconfirm
|
||||||
qt6-shadertools \
|
|
||||||
qt6-5compat \
|
|
||||||
qt6-canvaspainter
|
|
||||||
|
|
||||||
# Project-specific dependencies
|
|
||||||
RUN pacman -S --noconfirm \
|
|
||||||
libqalculate \
|
|
||||||
pipewire \
|
|
||||||
aubio \
|
|
||||||
cava \
|
|
||||||
lm_sensors \
|
|
||||||
glib2
|
|
||||||
|
|
||||||
# AUR: libcava (provides development headers for cava)
|
|
||||||
RUN pacman -Syu --noconfirm && \
|
|
||||||
pacman -S --needed --noconfirm \
|
|
||||||
sudo \
|
|
||||||
meson \
|
|
||||||
autoconf-archive \
|
|
||||||
python-tqdm && \
|
|
||||||
useradd -m builder && \
|
|
||||||
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
|
||||||
|
|
||||||
RUN for i in 1 2 3 4 5; do \
|
|
||||||
rm -rf /tmp/libcava && \
|
|
||||||
git clone --depth 1 https://aur.archlinux.org/libcava.git /tmp/libcava && \
|
|
||||||
break || sleep 5; \
|
|
||||||
done && \
|
|
||||||
test -d /tmp/libcava/.git && \
|
|
||||||
chown -R builder /tmp/libcava
|
|
||||||
|
|
||||||
RUN su builder -c "cd /tmp/libcava && makepkg -si --noconfirm" && \
|
|
||||||
rm -rf /tmp/libcava && \
|
|
||||||
userdel -r builder && \
|
|
||||||
sed -i '/^builder/d' /etc/sudoers
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
BUILD ?= cmake -B build -G Ninja
|
||||||
|
NONUITKA ?= cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_MODULES="plugin;shell;m3shapes"
|
||||||
|
NINJA ?= ninja -C build
|
||||||
|
COMPILE ?= $(BUILD) && $(NINJA)
|
||||||
|
INSTALL ?= $(BUILD) && $(NINJA) && sudo ninja -C build install
|
||||||
|
|
||||||
|
CPPFMT ?= find . \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -not -path './build/*' -exec clang-format -i --style=file {} +
|
||||||
|
CPPLINT ?= run-clang-tidy -p build -j "$$(nproc)" -source-filter="^$$(pwd)/Plugins/" -header-filter=".*Plugins/.*" -removed-arg=-mno-direct-extern-access
|
||||||
|
|
||||||
|
NUITKA ?= nuitka --module --include-package=zshell cli/src/zshell/ --output-dir=cli/
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
# string is misaligned to align when run in terminal
|
||||||
|
help:
|
||||||
|
@echo "build cmake builds into build directory"
|
||||||
|
@echo "install cmake builds, compiles and installs into filesystem"
|
||||||
|
@echo "compile cmake builds and compiles C++ code"
|
||||||
|
@echo "cpp-fmt format C++ code with clang-format"
|
||||||
|
@echo "cpp-lint lint check C++ code with clang-tidy"
|
||||||
|
@echo "cpp-compile similar to make compile, but excludes Nuitka from compiling python"
|
||||||
|
@echo "py-fmt format Python code with ruff"
|
||||||
|
@echo "py-lint lint check Python code with ruff"
|
||||||
|
@echo "py-check lsp and typecheck all Python code using Basedpyright"
|
||||||
|
@echo "py-test run all Python tests using Pytest"
|
||||||
|
@echo "py-compile compiles Python code using Nuitka"
|
||||||
|
@echo "rs-compile compiles Rust code"
|
||||||
|
@echo "rs-format formats Rust code with Rustfmt"
|
||||||
|
@echo "rs-lint lint check Rust code with Clippy"
|
||||||
|
@echo "rs-test runs all Rust tests"
|
||||||
|
@echo "qml-fmt format QML code with qmlformat"
|
||||||
|
@echo "check-cpp runs all C++ checks"
|
||||||
|
@echo "check-py runs all Python checks"
|
||||||
|
@echo "check-rs runs all Rust checks"
|
||||||
|
@echo "fmt-make formats the makefile using mbake (in case you like spaces instead of tabs)"
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(BUILD)
|
||||||
|
|
||||||
|
install:
|
||||||
|
$(INSTALL)
|
||||||
|
|
||||||
|
compile:
|
||||||
|
$(COMPILE)
|
||||||
|
|
||||||
|
# C++
|
||||||
|
cpp-fmt:
|
||||||
|
$(CPPFMT)
|
||||||
|
|
||||||
|
cpp-lint:
|
||||||
|
$(CPPLINT)
|
||||||
|
|
||||||
|
cpp-compile:
|
||||||
|
$(NONUITKA)
|
||||||
|
|
||||||
|
# Python
|
||||||
|
py-fmt:
|
||||||
|
ruff format --check
|
||||||
|
|
||||||
|
py-lint:
|
||||||
|
ruff check ./cli
|
||||||
|
|
||||||
|
py-check:
|
||||||
|
basedpyright
|
||||||
|
|
||||||
|
py-test:
|
||||||
|
python -m pytest ./cli/tests/ -v
|
||||||
|
|
||||||
|
# Want to use gcc, clang or zig C compiler instead? Output directory?
|
||||||
|
py-compile:
|
||||||
|
$(NUITKA)
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
rs-compile:
|
||||||
|
cd ./zshell-img-tools/ && cargo run
|
||||||
|
|
||||||
|
rs-fmt:
|
||||||
|
cd ./zshell-img-tools/ && cargo fmt && echo "formatted!"
|
||||||
|
|
||||||
|
rs-lint:
|
||||||
|
cd ./zshell-img-tools/ && cargo clippy
|
||||||
|
|
||||||
|
rs-test:
|
||||||
|
cd ./zshell-img-tools/ && cargo test
|
||||||
|
|
||||||
|
# Qml
|
||||||
|
qml-fmt:
|
||||||
|
find -name '*.qml' -exec /usr/lib/qt6/bin/qmlformat -i {} +
|
||||||
|
|
||||||
|
# Full checks
|
||||||
|
check-cpp:
|
||||||
|
$(MAKE) cpp-fmt
|
||||||
|
$(MAKE) cpp-lint
|
||||||
|
$(MAKE) compile
|
||||||
|
|
||||||
|
check-rs:
|
||||||
|
$(MAKE) rs-fmt
|
||||||
|
$(MAKE) rs-lint
|
||||||
|
$(MAKE) rs-test
|
||||||
|
$(MAKE) rs-compile
|
||||||
|
|
||||||
|
check-py:
|
||||||
|
$(MAKE) py-fmt
|
||||||
|
$(MAKE) py-lint
|
||||||
|
$(MAKE) py-test
|
||||||
|
$(MAKE) py-check
|
||||||
|
$(MAKE) py-compile
|
||||||
|
|
||||||
|
fmt-make:
|
||||||
|
mbake format makefile
|
||||||
Reference in New Issue
Block a user