mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Compare commits
407 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af8fb49dd0 | |||
| 8fd9fb73f2 | |||
| 8d83fbae67 | |||
| 414729dce5 | |||
| dbe79b7873 | |||
| 9438f59e2b | |||
| 719255ac35 | |||
| 8a51935224 | |||
| 8d583fe854 | |||
| 74d2b18603 | |||
| fad02316f1 | |||
| 47385c2ecd | |||
| e430d3ab2b | |||
| e472b5b0f1 | |||
| efb169416d | |||
| 3a3a97ec2a | |||
| e9c182a13c | |||
| cfe059c303 | |||
| bd7c748a4f | |||
| 68bb942d21 | |||
| 04c422e43f | |||
| d09fa2709c | |||
| 2c3315aebb | |||
| 5a45088061 | |||
| 404d6dccc4 | |||
| 084f2cb193 | |||
| 25c88b542f | |||
| 6fc50a1fb8 | |||
| 5e11b96f12 | |||
| 849d26d646 | |||
| 9e5716a9db | |||
| f4ebbc8017 | |||
| ce9dd33213 | |||
| 10995ec62c | |||
| c814c656c5 | |||
| 82d4c7569e | |||
| 4f0db78248 | |||
| 3e250cdc12 | |||
| a1b0bd6d1c | |||
| 892470afd3 | |||
| f1cb02cfab | |||
| 5dc4e83ba7 | |||
| 2b58e03d30 | |||
| d4b4407236 | |||
| 26ff5f4bf1 | |||
| d7905e6b74 | |||
| 71d7fa9a61 | |||
| 707f08559c | |||
| 4d21489101 | |||
| 5a24aae560 | |||
| 9170161a0a | |||
| 66d66d6030 | |||
| 19866f8b0b | |||
| 4bc3ede4b7 | |||
| 250aa1f3cb | |||
| 0117d6953d | |||
| 931123f38c | |||
| 73c0ce75d8 | |||
| 1b1715fe9b | |||
| fee8719299 | |||
| 7f9c7d1415 | |||
| b81cb13c2c | |||
| 45582ad095 | |||
| b3f5255bb9 | |||
| 8c169b1a14 | |||
| 525b33777b | |||
| dec0e3bf5a | |||
| 6bcaaf9d21 | |||
| f022b3c504 | |||
| ab10a260fa | |||
| 0eddd16b8a | |||
| d020d986ed | |||
| 5abeb923de | |||
| dd1f28998f | |||
| 874e7fd70e | |||
| 599db847f8 | |||
| d1a0380eed | |||
| 8f48f56fe1 | |||
| b07bde3ee8 | |||
| bf142e0b48 | |||
| 8f75d171b6 | |||
| cbf4631461 | |||
| a217ad6424 | |||
| f4dc10e0b4 | |||
| b82d52705e | |||
| c7fa5f29d6 | |||
| e708f54615 | |||
| 2dc6f4482c | |||
| a2a5291175 | |||
| 1fa0338a17 | |||
| 8e3e93b624 | |||
| c1146c0bef | |||
| 41b5de8769 | |||
| 8d9bc2a5c9 | |||
| 6d5c5f12b2 | |||
| 42b2aeb6e6 | |||
| ab47f5cec4 | |||
| 549148d277 | |||
| 189917c933 | |||
| f30db163b5 | |||
| a78f07cd58 | |||
| 765a241c5a | |||
| a00b271a15 | |||
| e1015ac92f | |||
| a34ed51586 | |||
| 5ddcf195dd | |||
| e11abe554f | |||
| 9261fd6342 | |||
| fb2f66f361 | |||
| e2e15b7a18 | |||
| 0a416eedda | |||
| d7184a04b9 | |||
| bdf394260a | |||
| 74d14be01f | |||
| 3ccb06f564 | |||
| d9e755d575 | |||
| 87e2dd0361 | |||
| dd93c39ed0 | |||
| 849788bb28 | |||
| 9015ff8e36 | |||
| e546b339a3 | |||
| b39edf405a | |||
| b98f4906da | |||
| e82830c68c | |||
| 238caaf8da | |||
| 9c79108afa | |||
| 2571242887 | |||
| 6f92b3296a | |||
| 570ea119ba | |||
| df4614e62c | |||
| 3672e79369 | |||
| 2d16abdaae | |||
| ff081acddc | |||
| afe27a143b | |||
| fd2916eb72 | |||
| e9d888cd52 | |||
| 05599ce2c4 | |||
| 0fb6c5706b | |||
| 79aaa4c6c0 | |||
| 7e559dc468 | |||
| 45fc763281 | |||
| 39d3cd2415 | |||
| 19b1074a8b | |||
| 539a5a8030 | |||
| 53b7477d20 | |||
| c34f7b18ec | |||
| a6baef7b68 | |||
| 10df9f4717 | |||
| 9f8eadc5bc | |||
| a496307daf | |||
| bc7bb51b6f | |||
| b7eb8a635b | |||
| d060b06667 | |||
| 54c2e2ab47 | |||
| df3f3979e9 | |||
| 6215b5f0b1 | |||
| 3bfa4a71ff | |||
| 3158f5a9c0 | |||
| d8250fa876 | |||
| cf0b4bc0ca | |||
| 1ab1737653 | |||
| b5640d5293 | |||
| 860a08cce6 | |||
| 2a9d0e495a | |||
| 7f132ecf95 | |||
| 1a63089d67 | |||
| 88dc6e22d0 | |||
| ce8171bed3 | |||
| 6edd29170f | |||
| 9d62b94688 | |||
| 4d295418ce | |||
| f01d48bc51 | |||
| 31ca509160 | |||
| 396097c3ab | |||
| ad62c8e487 | |||
| 9e73beb165 | |||
| 4fca614510 | |||
| 19e55a2df0 | |||
| 6472209b45 | |||
| d9ceff7c70 | |||
| 813c5ee05f | |||
| 47e217c00e | |||
| 9b52465e42 | |||
| 7d60231e35 | |||
| 7a237e519c | |||
| c4462d0c7f | |||
| f85cb5c5f9 | |||
| 7ca46b44b2 | |||
| f913219f94 | |||
| 80469abc20 | |||
| 890935d2ba | |||
| d2fa1f54d4 | |||
| 2641356d41 | |||
| 7c0898570c | |||
| d1fc1ab731 | |||
| d9a9e6ddc4 | |||
| 0cb20b55b8 | |||
| 3d2d7b95d9 | |||
| c22d8358c2 | |||
| 4d058e6111 | |||
| 83a733e085 | |||
| ba29735fbb | |||
| 6fc092cc4f | |||
| f874b2fce5 | |||
| 311ca6b5da | |||
| b35bcae35b | |||
| 284c2331a2 | |||
| ed266cc77c | |||
| 0652342df8 | |||
| e863f52fea | |||
| 8370c539fb | |||
| cbd0dfa280 | |||
| 54c7fdcd1a | |||
| 45b45ac29d | |||
| 9eeedff629 | |||
| 0920ea9f9f | |||
| 79e41d7d88 | |||
| d74debda13 | |||
| 642277f881 | |||
| c558516d4c | |||
| efbe29f3fa | |||
| 42922097a8 | |||
| cfc01b895c | |||
| 4e609f9319 | |||
| 87aacdbca7 | |||
| a977bb7feb | |||
| f46338c18b | |||
| 07c166ba7d | |||
| e107fddaa4 | |||
| 012700dd54 | |||
| 229716e144 | |||
| 07b387df46 | |||
| 98006a8dba | |||
| 2d82fdd9c8 | |||
| 0cd8484bdc | |||
| 9d522ed51e | |||
| 8ef5cc2297 | |||
| 46308bb58c | |||
| ade8dd502b | |||
| 5555e5578c | |||
| a21aac949c | |||
| cb47caba11 | |||
| 6fcdb9242e | |||
| 1ae489813e | |||
| c722634c26 | |||
| 63f58086b9 | |||
| a19326fd12 | |||
| 6ca21e7bfd | |||
| ce6d211485 | |||
| d7ce12a540 | |||
| 262cca6ad4 | |||
| c256842761 | |||
| 7f19d268b3 | |||
| 3eef19dc7d | |||
| 08fbdef60e | |||
| aecbd679e3 | |||
| 3769e5da46 | |||
| 933ffcb229 | |||
| b774fc1baf | |||
| 661fcd42ad | |||
| 9a23426551 | |||
| df6c0dff93 | |||
| 9dad215b9f | |||
| 2652efe9db | |||
| a52df533c4 | |||
| 5b77107161 | |||
| fb2f67c8a0 | |||
| 5c965d6ad6 | |||
| 978c4e6c8a | |||
| 9b11e39890 | |||
| 2144f9a6ae | |||
| a2ca2b3c86 | |||
| e257687921 | |||
| 94eeecac8c | |||
| 739834dfec | |||
| 7c0dcddee7 | |||
| 4c9df7eb5b | |||
| 6e8fd15339 | |||
| db32f52ce6 | |||
| 978aace94d | |||
| 66583cd650 | |||
| b01bc10e60 | |||
| 6a2c6261df | |||
| e6f3c538da | |||
| 4310c20c32 | |||
| b3245b81a6 | |||
| 31eb943599 | |||
| 137b87bbe5 | |||
| c8eea8ee9d | |||
| 23cd5aa78a | |||
| 8c8447918f | |||
| a4f5c90cf0 | |||
| 79cdbc5748 | |||
| d31a90edb0 | |||
| 8eeb7dd27e | |||
| ada6e844ba | |||
| b38df2d1c8 | |||
| 2776005c5f | |||
| ff09f7a558 | |||
| c27191ac64 | |||
| c482b446a9 | |||
| 1dd82a8dcf | |||
| 755aaac039 | |||
| 2d67ff8dc3 | |||
| 32d7323570 | |||
| 536204fd82 | |||
| e1fad994da | |||
| e5d4e7c1b1 | |||
| e420bb5ad7 | |||
| 4cb4d0fa92 | |||
| 1cede8922f | |||
| 80c22b805e | |||
| ad3847fad9 | |||
| 9bd860b393 | |||
| dd1ec83afa | |||
| 5ad3d661f0 | |||
| 87dc96fa69 | |||
| ba6e5e082a | |||
| 03c564736a | |||
| 1b41ef146d | |||
| 640e2b08a8 | |||
| 5c91e3191d | |||
| b7f1e382a2 | |||
| e5b0662f48 | |||
| 39339032ce | |||
| a250fcf252 | |||
| 24b3cbfe55 | |||
| c0f19d48fa | |||
| 31f9577df9 | |||
| 2eabc22b38 | |||
| 2de34e8798 | |||
| ceaf9408a5 | |||
| eff41ab716 | |||
| 0673260249 | |||
| 0a33dca5fd | |||
| b3ae3adbb7 | |||
| 264289cd41 | |||
| f975672255 | |||
| 67ca2cb06c | |||
| 9ff1c90fa6 | |||
| 09cf8402c3 | |||
| a5e285865b | |||
| d5f4e79e4c | |||
| d015c7e55b | |||
| 1465cd4139 | |||
| 7fc544b9d6 | |||
| 36dc5c6e8e | |||
| ea3bbea6bd | |||
| 3b4cf1ec8f | |||
| 1484175408 | |||
| 8c3345807a | |||
| c353a7dae9 | |||
| e3068cd483 | |||
| 6aa8146c32 | |||
| 4c524b4719 | |||
| 1fa9dd32ed | |||
| 2781d3a743 | |||
| e739ce8171 | |||
| a2727ba2c9 | |||
| 8df6231cc1 | |||
| e837e39623 | |||
| 3850739e44 | |||
| 4d4d968d97 | |||
| 6451d6be4f | |||
| 00a4e22566 | |||
| 19d21fc9b1 | |||
| a1dccedbb7 | |||
| 37aea69715 | |||
| 7024a23d2c | |||
| 86edeb3b0b | |||
| d9648e6bde | |||
| fcd10fea8e | |||
| ffb3030e36 | |||
| 4808ba2b20 | |||
| 35cbab476e | |||
| 28d072d893 | |||
| 276275ff76 | |||
| 82276771a5 | |||
| cd7108ef5d | |||
| d45b06385d | |||
| 919dcbe774 | |||
| a0d002c318 | |||
| d0e98d6e73 | |||
| 0b500334f9 | |||
| a3203d92ec | |||
| c832bdecdd | |||
| b8995a12d1 | |||
| 2f9832aa36 | |||
| 618c83c59f | |||
| 318f6ca8cc | |||
| c9c28aa497 | |||
| 5357db39cd | |||
| 08f5c6fecb | |||
| bffc5c1377 | |||
| c30e5c9185 | |||
| b2d2a766e4 | |||
| cd0d45fdb8 | |||
| e6a8ad3847 | |||
| b7909dbf61 | |||
| bdee1a6576 | |||
| 22f629c24b | |||
| c69464c128 | |||
| c08bffb092 | |||
| 0c5beaac40 | |||
| 77465e11fe | |||
| db419b4fc7 | |||
| 7c14063707 |
@@ -10,6 +10,13 @@ assignees: ''
|
||||
<!-- Please describe the issue here at the top, then fill in the system information below. -->
|
||||
|
||||
<!-- Attaching your full niri config can help diagnose the problem. -->
|
||||
<details><summary>Config</summary>
|
||||
|
||||
```kdl
|
||||
insert config here
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<!--
|
||||
If you have a problem with a specific app, please verify that it is running on Wayland, rather than X11. An easy way is to run xeyes and mouse over the app: xeyes will be able to "see" only X11 windows.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
contact_links:
|
||||
- name: Feature request
|
||||
url: https://github.com/YaLTeR/niri/discussions/new?category=ideas
|
||||
url: https://github.com/niri-wm/niri/discussions/new?category=ideas
|
||||
about: Ideas for new features and functionality (start a Discussion)
|
||||
- name: Ask a question
|
||||
url: https://github.com/YaLTeR/niri/discussions/new?category=q-a
|
||||
url: https://github.com/niri-wm/niri/discussions/new?category=q-a
|
||||
about: Question about niri (start a Discussion)
|
||||
- name: Matrix room
|
||||
url: https://matrix.to/#/#niri:matrix.org
|
||||
|
||||
@@ -13,10 +13,12 @@ updates:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "Andrew-Chen-Wang/github-wiki-action"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
+27
-31
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
container: alpine:3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
PROPTEST_MAX_SHRINK_ITERS: 200000
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y ${{ env.DEPS_APT }} libadwaita-1-dev
|
||||
|
||||
- uses: dtolnay/rust-toolchain@1.80.1
|
||||
- uses: dtolnay/rust-toolchain@1.85.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -230,10 +230,10 @@ jobs:
|
||||
|
||||
fedora:
|
||||
runs-on: ubuntu-24.04
|
||||
container: fedora:41
|
||||
container: fedora:42
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
@@ -251,36 +251,30 @@ jobs:
|
||||
CARGO_HOME: /home/runner/work/niri/niri/cargo-home
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
# Required for the rust-cache action to work.
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# FIXME: doesn't seem to cache the builds, only the downloads for some unknown reason.
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: true
|
||||
|
||||
# Remove man-db triggers to speed up Ubuntu upgrade by a minute or two during vmactions/freebsd-vm action run.
|
||||
- run: |
|
||||
sudo rm /var/lib/dpkg/info/man-db.*
|
||||
|
||||
- name: Build
|
||||
uses: vmactions/freebsd-vm@966989c456d41351f095a421f60e71342d3bce41 # v1.2.1
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
copyback: false
|
||||
prepare: |
|
||||
pkg update -f
|
||||
pkg install -y ${{ env.DEPS_PKG }}
|
||||
run: |
|
||||
curl -o patch-pipewire_init 'https://cgit.freebsd.org/ports/plain/x11-wm/niri/files/patch-pipewire_init?id=f3f7e555b06d9a87d63c047ce3e82e936a11f2fe'
|
||||
curl -o patch-pipewire_init 'https://cgit.freebsd.org/ports/plain/x11-wm/niri/files/patch-pipewire_init?id=cadf6784d264cf780b6e0ad59bd15b831d36cf80'
|
||||
|
||||
export CARGO_HOME="$PWD/cargo-home"
|
||||
|
||||
cargo fetch
|
||||
|
||||
( cd $CARGO_HOME/git/checkouts/pipewire-rs-*/*/; patch -p2 < $CARGO_HOME/../patch-pipewire_init; )
|
||||
( cd $CARGO_HOME/registry/src/index.crates.io-*/; patch -p1 < $CARGO_HOME/../patch-pipewire_init; )
|
||||
|
||||
cargo build \
|
||||
--offline \
|
||||
@@ -289,16 +283,18 @@ jobs:
|
||||
nix:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with:
|
||||
dotnet: false
|
||||
large-packages: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Check flake inputs
|
||||
uses: DeterminateSystems/flake-checker-action@v4
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@v3
|
||||
uses: cachix/install-nix-action@v31
|
||||
continue-on-error: true
|
||||
|
||||
- run: nix flake check
|
||||
@@ -312,7 +308,7 @@ jobs:
|
||||
contents: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
show-progress: false
|
||||
@@ -329,7 +325,7 @@ jobs:
|
||||
contents: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
show-progress: false
|
||||
|
||||
@@ -22,15 +22,18 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Check for unreplaced "Since:" in the wiki
|
||||
run: |
|
||||
if grep --recursive 'Since: next release' wiki; then
|
||||
exit 1
|
||||
fi
|
||||
# Fail if a match is found (exit code 0)
|
||||
grep --recursive 'Since: next release' docs/wiki && exit 1
|
||||
|
||||
# Fail if grep failed (exit code 2)
|
||||
status=$?
|
||||
if [ $status -ne 1 ]; then exit $status; fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
/target
|
||||
/result
|
||||
|
||||
.idea
|
||||
+20
-2
@@ -31,7 +31,7 @@ I would really appreciate help with testing and reviewing them.
|
||||
### Testing
|
||||
|
||||
Pick a pull request you like, then build it and give it a go.
|
||||
The [Developing niri wiki page](https://yalter.github.io/niri/Development:-Developing-niri) has guidance on running niri test builds.
|
||||
The [Developing niri wiki page](https://niri-wm.github.io/niri/Development:-Developing-niri) has guidance on running niri test builds.
|
||||
|
||||
Be really thorough with your testing.
|
||||
We're striving for polished features in niri, so point out any issues and bugs, even small ones like animation jank.
|
||||
@@ -84,12 +84,30 @@ When creating pull requests, please keep the following in mind.
|
||||
- When working on bigger features, I usually start with a big messy commit, then gradually split out smaller self-contained changes from it as the code gets into shape.
|
||||
- [git-rebase.io](https://git-rebase.io/) is a helpful guide for splitting commits and cleaning up history in git.
|
||||
- When you address a review comment, mark it as resolved.
|
||||
- Remember to [run tests](https://yalter.github.io/niri/Development:-Developing-niri#tests) and format the code with `cargo +nightly fmt --all`.
|
||||
- Remember to [run tests](https://niri-wm.github.io/niri/Development:-Developing-niri#tests) and format the code with `cargo +nightly fmt --all`.
|
||||
- For new layout actions, remember to add them to the randomized tests. For weird Wayland handling, adding client-server tests in `src/tests/` could be very useful.
|
||||
- Test your changes by hand thoroughly, including for edge cases and weird interactions. See the Testing section above for some tips.
|
||||
- Remember to document new config options on the wiki.
|
||||
- When opening a pull request, ensure "Allow edits from maintainers" is enabled, so I can make final tweaks before merging.
|
||||
|
||||
### How to get your pull request reviewed more quickly
|
||||
|
||||
- Make it small and self-contained. Avoid mixing several unrelated changes in one PR.
|
||||
- Split the PR into small and self-contained commits. This makes it much easier to review.
|
||||
- Discuss new features, options, or behavior changes beforehand; make sure there's consensus about the design.
|
||||
- When creating the pull request, clearly write what it does, what problem it solves, how to test it.
|
||||
- Follow the rest of the advice from this document.
|
||||
|
||||
## AI contributions
|
||||
|
||||
If you use LLMs for your contribution (issue, comment, pull request), then it is *your job* to check and clean up its output, just like with any other tool.
|
||||
*You* have to spend the time doing this.
|
||||
Particularly:
|
||||
|
||||
- If I can tell that a pull request is mostly LLM-generated, then very likely this pull request will take *significantly more time and effort* than usual to review and finish. This is based on my prior review experience. Therefore, I'm not interested in such pull requests—there's always plenty of human-written ones which take priority.
|
||||
- When using an LLM to prepare an issue, the text usually has a lot of unnecessary wording and irrelevant details. Anyone looking at such an issue will quickly lose interest in reading through it (myself certainly). Clean up the text and keep only those details that actually matter.
|
||||
- When using an LLM to comment on an issue, *you* have to verify that the comment makes sense, contributes something useful, and doesn't have unnecessary repetition.
|
||||
|
||||
|
||||
[cosmic-comp]: https://github.com/pop-os/cosmic-comp
|
||||
[anvil]: https://github.com/Smithay/smithay/tree/master/anvil
|
||||
|
||||
Generated
+1147
-1307
File diff suppressed because it is too large
Load Diff
+56
-49
@@ -6,34 +6,35 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "25.8.0"
|
||||
version = "26.4.0"
|
||||
description = "A scrollable-tiling Wayland compositor"
|
||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/YaLTeR/niri"
|
||||
rust-version = "1.80.1"
|
||||
repository = "https://github.com/niri-wm/niri"
|
||||
rust-version = "1.85"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.99"
|
||||
bitflags = "2.9.3"
|
||||
clap = { version = "4.5.46", features = ["derive"] }
|
||||
insta = "1.43.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.143"
|
||||
tracing = { version = "0.1.41", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tracy-client = { version = "0.18.2", default-features = false }
|
||||
anyhow = "1.0.102"
|
||||
bitflags = "2.11.1"
|
||||
clap = { version = "4.6.1", features = ["derive"] }
|
||||
insta = "1.47.2"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
tracing = { version = "0.1.44", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
tracy-client = { version = "0.18.4", default-features = false }
|
||||
|
||||
[workspace.dependencies.smithay]
|
||||
# version = "0.4.1"
|
||||
git = "https://github.com/Smithay/smithay.git"
|
||||
# path = "../smithay"
|
||||
rev = "ff5fa7df392cecfba049ffed55cdaa4e98a8e7ef"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.smithay-drm-extras]
|
||||
# version = "0.1.0"
|
||||
git = "https://github.com/Smithay/smithay.git"
|
||||
rev = "ff5fa7df392cecfba049ffed55cdaa4e98a8e7ef"
|
||||
# path = "../smithay/smithay-drm-extras"
|
||||
|
||||
[package]
|
||||
@@ -50,51 +51,53 @@ readme = "README.md"
|
||||
keywords = ["wayland", "compositor", "tiling", "smithay", "wm"]
|
||||
|
||||
[dependencies]
|
||||
accesskit = { version = "0.21.0", optional = true }
|
||||
accesskit_unix = { version = "0.17.0", optional = true }
|
||||
# accesskit_unix 0.18 has a regression where it doesn't work in normal configurations.
|
||||
# accesskit 0.21 is its correct dependent version.
|
||||
# https://github.com/niri-wm/niri/issues/3594
|
||||
accesskit = { version = "0.21", optional = true }
|
||||
accesskit_unix = { version = "0.17", optional = true }
|
||||
anyhow.workspace = true
|
||||
arrayvec = "0.7.6"
|
||||
async-channel = "2.5.0"
|
||||
async-io = { version = "2.5.0", optional = true }
|
||||
async-io = { version = "2.6.0", optional = true }
|
||||
atomic = "0.6.1"
|
||||
bitflags.workspace = true
|
||||
bytemuck = { version = "1.23.2", features = ["derive"] }
|
||||
calloop = { version = "0.14.3", features = ["executor", "futures-io", "signals"] }
|
||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||
calloop = { version = "0.14.4", features = ["executor", "futures-io", "signals"] }
|
||||
clap = { workspace = true, features = ["string"] }
|
||||
clap_complete = "4.5.57"
|
||||
clap_complete_nushell = "4.5.8"
|
||||
clap_complete = "4.6.2"
|
||||
clap_complete_nushell = "4.6.0"
|
||||
directories = "6.0.0"
|
||||
drm-ffi = "0.9.0"
|
||||
fastrand = "2.3.0"
|
||||
futures-util = { version = "0.3.31", default-features = false, features = ["std", "io"] }
|
||||
drm-ffi = "0.9.1"
|
||||
fastrand = "2.4.1"
|
||||
futures-util = { version = "0.3.32", default-features = false, features = ["std", "io"] }
|
||||
git-version = "0.3.9"
|
||||
glam = "0.30.5"
|
||||
input = { version = "0.9.1", features = ["libinput_1_21"] }
|
||||
glam = "0.32.1"
|
||||
input = { version = "0.10.0", features = ["libinput_1_21"] }
|
||||
keyframe = { version = "1.1.1", default-features = false }
|
||||
libc = "0.2.175"
|
||||
libdisplay-info = "0.2.2"
|
||||
log = { version = "0.4.27", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
niri-config = { version = "25.8.0", path = "niri-config" }
|
||||
niri-ipc = { version = "25.8.0", path = "niri-ipc", features = ["clap"] }
|
||||
ordered-float = "5.0.0"
|
||||
pango = { version = "0.20.12", features = ["v1_44"] }
|
||||
pangocairo = "0.20.10"
|
||||
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", optional = true, features = ["v0_3_33"] }
|
||||
png = "0.17.16"
|
||||
portable-atomic = { version = "1.11.1", default-features = false, features = ["float"] }
|
||||
libc = "0.2.185"
|
||||
libdisplay-info = "0.3.0"
|
||||
log = { version = "0.4.29", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
niri-config = { version = "26.4.0", path = "niri-config" }
|
||||
niri-ipc = { version = "26.4.0", path = "niri-ipc", features = ["clap"] }
|
||||
ordered-float = "5.3.0"
|
||||
pango = { version = "0.21.5", features = ["v1_44"] }
|
||||
pangocairo = "0.21.5"
|
||||
pipewire = { version = "0.9.2", optional = true, features = ["v0_3_33"] }
|
||||
png = "0.18.1"
|
||||
profiling = "1.0.17"
|
||||
sd-notify = "0.4.5"
|
||||
sd-notify = "0.5.0"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smithay-drm-extras.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing.workspace = true
|
||||
tracy-client.workspace = true
|
||||
url = { version = "2.5.7", optional = true }
|
||||
wayland-backend = "0.3.11"
|
||||
wayland-scanner = "0.31.7"
|
||||
wayland-backend = "0.3.15"
|
||||
wayland-scanner = "0.31.10"
|
||||
wayland-server = "0.31.13"
|
||||
xcursor = "0.3.10"
|
||||
zbus = { version = "5.10.0", optional = true }
|
||||
zbus = { version = "5.13.2", optional = true }
|
||||
|
||||
[dependencies.smithay]
|
||||
workspace = true
|
||||
@@ -116,24 +119,27 @@ features = [
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
calloop-wayland-source = "0.4.0"
|
||||
calloop-wayland-source = "0.4.1"
|
||||
insta.workspace = true
|
||||
proptest = "1.7.0"
|
||||
proptest-derive = { version = "0.6.0", features = ["boxed_union"] }
|
||||
rayon = "1.11.0"
|
||||
wayland-client = "0.31.11"
|
||||
proptest = "1.11.0"
|
||||
proptest-derive = { version = "0.8.0", features = ["boxed_union"] }
|
||||
rayon = "1.12.0"
|
||||
wayland-client = "0.31.14"
|
||||
xshell = "0.2.7"
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3.33"
|
||||
|
||||
[features]
|
||||
default = ["dbus", "systemd", "xdp-gnome-screencast"]
|
||||
# Enables D-Bus support (serve various freedesktop and GNOME interfaces, accessibility tree, power button handling).
|
||||
dbus = ["dep:zbus", "dep:async-io", "dep:url", "dep:accesskit", "dep:accesskit_unix"]
|
||||
dbus = ["dep:zbus", "dep:async-io", "dep:accesskit", "dep:accesskit_unix"]
|
||||
# Enables systemd integration (global environment, apps in transient scopes).
|
||||
systemd = ["dbus"]
|
||||
# Enables screencasting support through xdg-desktop-portal-gnome.
|
||||
xdp-gnome-screencast = ["dbus", "pipewire"]
|
||||
# Enables the Tracy profiler instrumentation.
|
||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default", "smithay/tracy_gpu_profiling"]
|
||||
# Enables the on-demand Tracy profiler instrumentation.
|
||||
profile-with-tracy-ondemand = ["profile-with-tracy", "tracy-client/ondemand", "tracy-client/manual-lifetime"]
|
||||
# Enables Tracy allocation profiling.
|
||||
@@ -143,6 +149,7 @@ dinit = []
|
||||
|
||||
[lints.clippy]
|
||||
new_without_default = "allow"
|
||||
collapsible_match = "allow"
|
||||
|
||||
[profile.release]
|
||||
debug = "line-tables-only"
|
||||
@@ -158,7 +165,7 @@ insta.opt-level = 3
|
||||
similar.opt-level = 3
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
version = "25.05.1"
|
||||
version = "26.04"
|
||||
assets = [
|
||||
{ source = "target/release/niri", dest = "/usr/bin/", mode = "755" },
|
||||
{ source = "resources/niri-session", dest = "/usr/bin/", mode = "755" },
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<p align="center">A scrollable-tiling Wayland compositor.</p>
|
||||
<p align="center">
|
||||
<a href="https://matrix.to/#/#niri:matrix.org"><img alt="Matrix" src="https://img.shields.io/badge/matrix-%23niri-blue?logo=matrix"></a>
|
||||
<a href="https://github.com/YaLTeR/niri/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/YaLTeR/niri"></a>
|
||||
<a href="https://github.com/YaLTeR/niri/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/YaLTeR/niri?logo=github"></a>
|
||||
<a href="https://github.com/niri-wm/niri/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/niri-wm/niri"></a>
|
||||
<a href="https://github.com/niri-wm/niri/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/niri-wm/niri?logo=github"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://yalter.github.io/niri/Getting-Started.html">Getting Started</a> | <a href="https://yalter.github.io/niri/Configuration%3A-Introduction.html">Configuration</a> | <a href="https://github.com/YaLTeR/niri/discussions/325">Setup Showcase</a>
|
||||
<a href="https://niri-wm.github.io/niri/Getting-Started.html">Getting Started</a> | <a href="https://niri-wm.github.io/niri/Configuration%3A-Introduction.html">Configuration</a> | <a href="https://github.com/niri-wm/niri/discussions/325">Setup Showcase</a>
|
||||
</p>
|
||||
|
||||

|
||||
@@ -29,23 +29,23 @@ When a monitor disconnects, its workspaces will move to another monitor, but upo
|
||||
## Features
|
||||
|
||||
- Built from the ground up for scrollable tiling
|
||||
- [Dynamic workspaces](https://yalter.github.io/niri/Workspaces.html) like in GNOME
|
||||
- [Dynamic workspaces](https://niri-wm.github.io/niri/Workspaces.html) like in GNOME
|
||||
- An [Overview](https://github.com/user-attachments/assets/379a5d1f-acdb-4c11-b36c-e85fd91f0995) that zooms out workspaces and windows
|
||||
- Built-in screenshot UI
|
||||
- Monitor and window screencasting through xdg-desktop-portal-gnome
|
||||
- You can [block out](https://yalter.github.io/niri/Configuration%3A-Window-Rules.html#block-out-from) sensitive windows from screencasts
|
||||
- [Dynamic cast target](https://yalter.github.io/niri/Screencasting.html#dynamic-screencast-target) that can change what it shows on the go
|
||||
- [Touchpad](https://github.com/YaLTeR/niri/assets/1794388/946a910e-9bec-4cd1-a923-4a9421707515) and [mouse](https://github.com/YaLTeR/niri/assets/1794388/8464e65d-4bf2-44fa-8c8e-5883355bd000) gestures
|
||||
- Group windows into [tabs](https://yalter.github.io/niri/Tabs.html)
|
||||
- You can [block out](https://niri-wm.github.io/niri/Configuration%3A-Window-Rules.html#block-out-from) sensitive windows from screencasts
|
||||
- [Dynamic cast target](https://niri-wm.github.io/niri/Screencasting.html#dynamic-screencast-target) that can change what it shows on the go
|
||||
- [Touchpad](https://github.com/niri-wm/niri/assets/1794388/946a910e-9bec-4cd1-a923-4a9421707515) and [mouse](https://github.com/niri-wm/niri/assets/1794388/8464e65d-4bf2-44fa-8c8e-5883355bd000) gestures
|
||||
- Group windows into [tabs](https://niri-wm.github.io/niri/Tabs.html)
|
||||
- Configurable layout: gaps, borders, struts, window sizes
|
||||
- [Gradient borders](https://yalter.github.io/niri/Configuration%3A-Layout.html#gradients) with Oklab and Oklch support
|
||||
- [Animations](https://github.com/YaLTeR/niri/assets/1794388/ce178da2-af9e-4c51-876f-8709c241d95e) with support for [custom shaders](https://github.com/YaLTeR/niri/assets/1794388/27a238d6-0a22-4692-b794-30dc7a626fad)
|
||||
- [Gradient borders](https://niri-wm.github.io/niri/Configuration%3A-Layout.html#gradients) with Oklab and Oklch support
|
||||
- [Animations](https://github.com/niri-wm/niri/assets/1794388/ce178da2-af9e-4c51-876f-8709c241d95e) with support for [custom shaders](https://github.com/niri-wm/niri/assets/1794388/27a238d6-0a22-4692-b794-30dc7a626fad)
|
||||
- Live-reloading config
|
||||
- Works with [screen readers](https://yalter.github.io/niri/Accessibility.html)
|
||||
- Works with [screen readers](https://niri-wm.github.io/niri/Accessibility.html)
|
||||
|
||||
## Video Demo
|
||||
|
||||
https://github.com/YaLTeR/niri/assets/1794388/bce834b0-f205-434e-a027-b373495f9729
|
||||
https://github.com/niri-wm/niri/assets/1794388/bce834b0-f205-434e-a027-b373495f9729
|
||||
|
||||
Also check out this video from Brodie Robertson that showcases a lot of the niri functionality: [Niri Is My New Favorite Wayland Compositor](https://youtu.be/DeYx2exm04M)
|
||||
|
||||
@@ -55,7 +55,7 @@ Niri is stable for day-to-day use and does most things expected of a Wayland com
|
||||
Many people are daily-driving niri, and are happy to help in our [Matrix channel].
|
||||
|
||||
Give it a try!
|
||||
Follow the instructions on the [Getting Started](https://yalter.github.io/niri/Getting-Started.html) page.
|
||||
Follow the instructions on the [Getting Started](https://niri-wm.github.io/niri/Getting-Started.html) page.
|
||||
Have your [waybar]s and [fuzzel]s ready: niri is not a complete desktop environment.
|
||||
Also check out [awesome-niri], a list of niri-related links and projects.
|
||||
|
||||
@@ -72,7 +72,7 @@ We have touchpad gestures, but no touchscreen gestures yet.
|
||||
You can check on [wayland.app](https://wayland.app) at the bottom of each protocol's page.
|
||||
- **Performance**: while I run niri on beefy machines, I try to stay conscious of performance.
|
||||
I've seen someone use it fine on an Eee PC 900 from 2008, of all things.
|
||||
- **Xwayland**: [integrated](https://yalter.github.io/niri/Xwayland.html#using-xwayland-satellite) via xwayland-satellite starting from niri 25.08.
|
||||
- **Xwayland**: [integrated](https://niri-wm.github.io/niri/Xwayland.html#using-xwayland-satellite) via xwayland-satellite starting from niri 25.08.
|
||||
|
||||
## Media
|
||||
|
||||
@@ -93,7 +93,7 @@ An LWN article with a nice overview and introduction to niri.
|
||||
## Contributing
|
||||
|
||||
If you'd like to help with niri, there are plenty of both coding- and non-coding-related ways to do so.
|
||||
See [CONTRIBUTING.md](https://github.com/YaLTeR/niri/blob/main/CONTRIBUTING.md) for an overview.
|
||||
See [CONTRIBUTING.md](https://github.com/niri-wm/niri/blob/main/CONTRIBUTING.md) for an overview.
|
||||
|
||||
## Inspiration
|
||||
|
||||
@@ -121,7 +121,7 @@ We also have a community Discord server: https://discord.gg/vT8Sfjy7sx
|
||||
[PaperWM]: https://github.com/paperwm/PaperWM
|
||||
[waybar]: https://github.com/Alexays/Waybar
|
||||
[fuzzel]: https://codeberg.org/dnkl/fuzzel
|
||||
[awesome-niri]: https://github.com/Vortriz/awesome-niri
|
||||
[awesome-niri]: https://github.com/niri-wm/awesome-niri
|
||||
[karousel]: https://github.com/peterfajdiga/karousel
|
||||
[papersway]: https://spwhitton.name/tech/code/papersway/
|
||||
[hyprscrolling]: https://github.com/hyprwm/hyprland-plugins/tree/main/hyprscrolling
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-check-cfg=cfg(have_libinput_plugin_system)");
|
||||
if pkg_config::Config::new()
|
||||
.atleast_version("1.30.0")
|
||||
.probe("libinput")
|
||||
.is_ok()
|
||||
{
|
||||
println!("cargo:rustc-cfg=have_libinput_plugin_system")
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,5 @@ def _badge_for_version(preposition: str, version: str):
|
||||
# we might fail to make real links to release notes on other cases too, but for now this is the one i've found
|
||||
return f"<span class=\"badge\">{preposition}: {version}</span>"
|
||||
else:
|
||||
path = f"https://github.com/YaLTeR/niri/releases/tag/v{version}"
|
||||
path = f"https://github.com/niri-wm/niri/releases/tag/v{version}"
|
||||
return f"<span class=\"badge\">[{preposition}: {version}]({path})</span>"
|
||||
|
||||
+7
-2
@@ -1,7 +1,7 @@
|
||||
site_name: niri
|
||||
docs_dir: wiki
|
||||
site_url: https://yalter.github.io/niri
|
||||
repo_url: https://github.com/YaLTeR/niri
|
||||
site_url: https://niri-wm.github.io/niri
|
||||
repo_url: https://github.com/niri-wm/niri
|
||||
edit_uri: edit/main/docs/wiki/
|
||||
use_directory_urls: false
|
||||
|
||||
@@ -84,9 +84,12 @@ nav:
|
||||
- Nvidia: Nvidia.md
|
||||
- Xwayland: Xwayland.md
|
||||
- Gestures: Gestures.md
|
||||
- Fullscreen and Maximize: Fullscreen-and-Maximize.md
|
||||
- Window Effects: Window-Effects.md
|
||||
- Packaging niri: Packaging-niri.md
|
||||
- Integrating niri: Integrating-niri.md
|
||||
- Accessibility: Accessibility.md
|
||||
- Name and Logo: Name-and-Logo.md
|
||||
- FAQ: FAQ.md
|
||||
- Configuration:
|
||||
- Introduction: Configuration:-Introduction.md
|
||||
@@ -101,7 +104,9 @@ nav:
|
||||
- Layer Rules: Configuration:-Layer-Rules.md
|
||||
- Animations: Configuration:-Animations.md
|
||||
- Gestures: Configuration:-Gestures.md
|
||||
- Recent Windows: Configuration:-Recent-Windows.md
|
||||
- Debug Options: Configuration:-Debug-Options.md
|
||||
- Include: Configuration:-Include.md
|
||||
- Development:
|
||||
- Design Principles: Development:-Design-Principles.md
|
||||
- Developing niri: Development:-Developing-niri.md
|
||||
|
||||
+1
-1
@@ -9,6 +9,6 @@ dependencies = [
|
||||
]
|
||||
|
||||
# for KDL highlighting support
|
||||
# TODO: use the official pygments package once https://github.com/pygments/pygments/pull/2936 is merged
|
||||
# FIXME: use the official pygments package once https://github.com/pygments/pygments/pull/2936 is merged
|
||||
[tool.uv.sources]
|
||||
pygments = { git = "https://github.com/chinatsu/pygments", rev = "0f0b0d4da2839e1285881389155bb4605a0a6dc4" }
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Niri has basic support for screen readers (specifically, [Orca](https://orca.gnome.org)) when running as a full desktop session, i.e. not as a nested window.
|
||||
Niri has basic support for screen readers (specifically, [Orca](https://orca.gnome.org)) when running as a full desktop session, i.e. you need to start niri through a display manager or through `niri-session`.
|
||||
To avoid conflicts with an already running compositor, niri won't expose accessibility interfaces when started as a nested window, or as a plain `/usr/bin/niri` on a TTY.
|
||||
|
||||
We implement the `org.freedesktop.a11y.KeyboardMonitor` D-Bus interface for Orca to listen and grab keyboard keys, and we expose the main niri UI elements via [AccessKit](https://accesskit.dev).
|
||||
Specifically, niri will announce:
|
||||
|
||||
- workspace switching, for example it'll say "Workspace 2";
|
||||
- workspace switching, for example it'll say "Workspace 2" when you switch to the second workspace;
|
||||
- the exit confirmation dialog (appears on <kbd>Super</kbd><kbd>Shift</kbd><kbd>E</kbd> by default);
|
||||
- <sup>Since: 25.11</sup> niri has an <kbd>Alt</kbd><kbd>Tab</kbd> window switcher where it will announce the selected window title;
|
||||
- entering the screenshot UI and the overview (niri will say when these are focused, nothing else for now);
|
||||
- whenever a config parse error occurs;
|
||||
- the important hotkeys list (for now, as one big announcement without tab navigation; appears on <kbd>Super</kbd><kbd>Shift</kbd><kbd>/</kbd> by default).
|
||||
@@ -23,12 +26,17 @@ https://github.com/user-attachments/assets/afceba6f-79f1-47ec-b859-a0fcb7f8eae3
|
||||
Make sure [Xwayland](./Xwayland.md) works, then run `orca`.
|
||||
The default config binds <kbd>Super</kbd><kbd>Alt</kbd><kbd>S</kbd> to toggle Orca, which is the standard key binding.
|
||||
|
||||
Note that we don't have an Alt-Tab window switcher yet (it's in the works), and we also don't have a bind to move focus to layer-shell panels.
|
||||
Note that there are some limitations:
|
||||
|
||||
- We don't have a bind to move focus to layer-shell panels. This is not hard to add, but it would be good to have some consensus or prior art with LXQt/Xfce on how exactly this should work.
|
||||
- You need to have a screen connected and enabled. Without a screen, niri won't give focus any window. This makes sense for sighted users, and I'm not entirely sure what makes the most sense for accessibility purposes (maybe, it'd be better solved with virtual monitors).
|
||||
- You need working EGL (hardware acceleration).
|
||||
- We don't have screen curtain functionality yet.
|
||||
|
||||
If you're shipping niri and would like to make it work better for screen readers out of the box, consider the following changes to the default niri config:
|
||||
|
||||
- Change the default terminal from Alacritty to one that supports screen readers. For example, [GNOME Console](https://gitlab.gnome.org/GNOME/console) or [GNOME Terminal](https://gitlab.gnome.org/GNOME/gnome-terminal) should work well.
|
||||
- Change the default application launcher and screen locker to ones that support screen readers. Suggestions welcome! Likely, something GTK-based will work fine.
|
||||
- Change the default application launcher and screen locker to ones that support screen readers. For example, [xfce4-appfinder](https://docs.xfce.org/xfce/xfce4-appfinder/start) is an accessible launcher. Suggestions welcome! Likely, something GTK-based will work fine.
|
||||
- Add some [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup) command that plays a sound which will indicate to users that niri has finished loading.
|
||||
- Add `spawn-at-startup "orca"` to run Orca automatically at niri startup.
|
||||
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
|
||||
Electron-based applications can run directly on Wayland, but it's not the default.
|
||||
|
||||
For Electron > 28, you can set an environment variable:
|
||||
For Electron ≥ 39, you can use the command-line flag if the app does not default to Wayland:
|
||||
```
|
||||
--ozone-platform=wayland
|
||||
```
|
||||
|
||||
For Electron < 39, you can set an environment variable:
|
||||
```kdl
|
||||
environment {
|
||||
ELECTRON_OZONE_PLATFORM_HINT "auto"
|
||||
}
|
||||
```
|
||||
|
||||
For previous versions, you need to pass command-line flags to the target application:
|
||||
For Electron ≤ 28, you need to pass command-line flags to the target application:
|
||||
```
|
||||
--enable-features=UseOzonePlatform --ozone-platform-hint=auto
|
||||
```
|
||||
@@ -22,6 +27,12 @@ If you're having issues with some VSCode hotkeys, try starting `Xwayland` and se
|
||||
That is, still running VSCode with the Wayland backend, but with `DISPLAY` set to a running Xwayland instance.
|
||||
Apparently, VSCode currently unconditionally queries the X server for a keymap.
|
||||
|
||||
### JetBrains IDEs
|
||||
|
||||
JetBrains IDEs can run directly on Wayland, but it's not the default.
|
||||
|
||||
For JetBrainsRuntime > 17, you can set the flag `-Dawt.toolkit.name=WLToolkit` inside of `help -> edit custom vm options -> add`.
|
||||
|
||||
### WezTerm
|
||||
|
||||
> [!NOTE]
|
||||
@@ -47,16 +58,25 @@ So if you hit this problem, comment out `prefer-no-csd` in the niri config and r
|
||||
Some Java apps like Ghidra can show up blank under xwayland-satellite.
|
||||
To fix this, run them with the `_JAVA_AWT_WM_NONREPARENTING=1` environment variable.
|
||||
|
||||
### rofi-wayland
|
||||
|
||||
There's a bug in rofi-wayland that prevents it from accepting keyboard input on niri with errors in the output.
|
||||
It's been fixed in rofi, but [the fix had not been released yet](https://github.com/davatorium/rofi/discussions/2008).
|
||||
|
||||
### Zen Browser
|
||||
|
||||
For some reason, DMABUF screencasts are disabled in the Zen Browser, so screencasting doesn't work out of the box on niri.
|
||||
To fix it, open `about:config` and set `widget.dmabuf.force-enabled` to `true`.
|
||||
|
||||
### GTK 4 dead keys / Compose
|
||||
|
||||
GTK 4.20 [stopped](https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8556) handling dead keys and Compose on its own on Wayland.
|
||||
To make them work, either run an IME like IBus or Fcitx5, or set the `GTK_IM_MODULE=simple` environment variable.
|
||||
|
||||
```kdl
|
||||
environment {
|
||||
GTK_IM_MODULE "simple"
|
||||
}
|
||||
```
|
||||
|
||||
Note that the niri environment config does not propagate to apps and shells started by systemd, for example to DankMaterialShell and its application launcher.
|
||||
You can set the variable in your login shell config (i.e. `~/.bash_profile`) instead, though keep in mind that then it will be set for all compositors, not just niri.
|
||||
|
||||
### Fullscreen games
|
||||
|
||||
Some video games, both Linux-native and on Wine, have various issues when using non-stacking desktop environments.
|
||||
|
||||
@@ -58,6 +58,10 @@ animations {
|
||||
overview-open-close {
|
||||
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
|
||||
}
|
||||
|
||||
recent-windows-close {
|
||||
spring damping-ratio=1.0 stiffness=800 epsilon=0.001
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -422,6 +426,20 @@ animations {
|
||||
}
|
||||
```
|
||||
|
||||
#### `recent-windows-close`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
The close fade-out animation of the recent windows switcher.
|
||||
|
||||
```kdl
|
||||
animations {
|
||||
recent-windows-close {
|
||||
spring damping-ratio=1.0 stiffness=800 epsilon=0.001
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Synchronized Animations
|
||||
|
||||
<sup>Since: 0.1.5</sup>
|
||||
|
||||
@@ -17,7 +17,10 @@ debug {
|
||||
disable-cursor-plane
|
||||
disable-direct-scanout
|
||||
restrict-primary-scanout-to-matching-format
|
||||
force-disable-connectors-on-resume
|
||||
render-drm-device "/dev/dri/renderD129"
|
||||
ignore-drm-device "/dev/dri/renderD128"
|
||||
ignore-drm-device "/dev/dri/renderD130"
|
||||
force-pipewire-invalid-modifier
|
||||
dbus-interfaces-in-non-session-instances
|
||||
wait-for-frame-completion-before-queueing
|
||||
@@ -30,7 +33,6 @@ debug {
|
||||
honor-xdg-activation-with-invalid-serial
|
||||
skip-cursor-only-updates-during-vrr
|
||||
deactivate-unfocused-windows
|
||||
keep-max-bpc-unchanged
|
||||
}
|
||||
|
||||
binds {
|
||||
@@ -103,6 +105,21 @@ debug {
|
||||
}
|
||||
```
|
||||
|
||||
### `force-disable-connectors-on-resume`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Force-disables all outputs upon resuming niri (TTY switch or waking up from suspend).
|
||||
This causes a modeset/screen blank on all outputs.
|
||||
|
||||
If niri rendering is corrupted, or monitors don't light up after a TTY switch, you can try this flag.
|
||||
|
||||
```kdl
|
||||
debug {
|
||||
force-disable-connectors-on-resume
|
||||
}
|
||||
```
|
||||
|
||||
### `render-drm-device`
|
||||
|
||||
Override the DRM device that niri will use for all rendering.
|
||||
@@ -115,6 +132,20 @@ debug {
|
||||
}
|
||||
```
|
||||
|
||||
### `ignore-drm-device`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
List DRM devices that niri will ignore.
|
||||
Useful for GPU passthrough when you don't want niri to open a certain device.
|
||||
|
||||
```kdl
|
||||
debug {
|
||||
ignore-drm-device "/dev/dri/renderD128"
|
||||
ignore-drm-device "/dev/dri/renderD130"
|
||||
}
|
||||
```
|
||||
|
||||
### `force-pipewire-invalid-modifier`
|
||||
|
||||
<sup>Since: 25.01</sup>
|
||||
@@ -304,6 +335,10 @@ Apparently, setting max bpc to 8 breaks some displays driven by AMDGPU.
|
||||
If this happens to you, set this debug flag, which will prevent niri from changing max bpc.
|
||||
AMDGPU bug report: https://gitlab.freedesktop.org/drm/amd/-/issues/4487.
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
This setting is deprecated and does nothing: niri no longer sets max bpc.
|
||||
The old niri behavior with this setting enabled matches the new behavior.
|
||||
|
||||
```kdl
|
||||
debug {
|
||||
keep-max-bpc-unchanged
|
||||
|
||||
@@ -23,6 +23,10 @@ gestures {
|
||||
|
||||
hot-corners {
|
||||
// off
|
||||
top-left
|
||||
// top-right
|
||||
// bottom-left
|
||||
// bottom-right
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -94,3 +98,18 @@ gestures {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<sup>Since: 25.11</sup> You can choose specific hot corners by name: `top-left`, `top-right`, `bottom-left`, `bottom-right`.
|
||||
If no corners are explicitly set, the top-left corner will be active by default.
|
||||
|
||||
```kdl
|
||||
// Enable the top-right and bottom-right hot corners.
|
||||
gestures {
|
||||
hot-corners {
|
||||
top-right
|
||||
bottom-right
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also customize hot corners per-output [in the output config](./Configuration:-Outputs.md#hot-corners).
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
You can include other files at the top level of the config.
|
||||
|
||||
```kdl,must-fail
|
||||
// Some settings...
|
||||
|
||||
include "colors.kdl"
|
||||
|
||||
// Some more settings...
|
||||
```
|
||||
|
||||
Included files have the same structure as the main config file.
|
||||
Settings from included files will be merged with the settings from the main config file.
|
||||
|
||||
Included config files can in turn include more files.
|
||||
All included files are watched for changes, and the config live-reloads when any of them change.
|
||||
|
||||
You can include by filename or path.
|
||||
|
||||
* Relative to the current file: `other.kdl` or `./other.kdl`
|
||||
* By absolute path: `/path/to/file.kdl`
|
||||
* <sup>Since: 26.04</sup> Home dir paths: `~/file.kdl` expands to `/home/user/file.kdl`
|
||||
|
||||
Includes work only at the top level of the config:
|
||||
|
||||
```kdl,must-fail
|
||||
// All good: include at the top level.
|
||||
include "something.kdl"
|
||||
|
||||
layout {
|
||||
// NOT allowed: include inside some other section.
|
||||
include "other.kdl"
|
||||
}
|
||||
```
|
||||
|
||||
### Positionality
|
||||
|
||||
Includes are *positional*.
|
||||
They will override options set *prior* to them.
|
||||
Window rules from included files will be inserted at the position of the `include` line.
|
||||
For example:
|
||||
|
||||
```kdl
|
||||
// colors.kdl
|
||||
layout {
|
||||
border {
|
||||
active-color "green"
|
||||
}
|
||||
}
|
||||
|
||||
overview {
|
||||
backdrop-color "green"
|
||||
}
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
layout {
|
||||
border {
|
||||
active-color "red"
|
||||
}
|
||||
}
|
||||
|
||||
// This overrides the border color and the backdrop color to green.
|
||||
include "colors.kdl"
|
||||
|
||||
// This sets the overview backdrop color to red again.
|
||||
overview {
|
||||
backdrop-color "red"
|
||||
}
|
||||
```
|
||||
|
||||
The end result:
|
||||
|
||||
- the border color is green (from `colors.kdl`),
|
||||
- the overview backdrop color is red (it was set *after* `colors.kdl`).
|
||||
|
||||
Another example:
|
||||
|
||||
```kdl
|
||||
// rules.kdl
|
||||
window-rule {
|
||||
match app-id="Alacritty"
|
||||
open-maximized false
|
||||
}
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
window-rule {
|
||||
open-maximized true
|
||||
}
|
||||
|
||||
// Window rules get inserted at this position.
|
||||
include "rules.kdl"
|
||||
|
||||
window-rule {
|
||||
match app-id="firefox$"
|
||||
open-maximized true
|
||||
}
|
||||
```
|
||||
|
||||
This is equivalent to the following config file:
|
||||
|
||||
```kdl
|
||||
window-rule {
|
||||
open-maximized true
|
||||
}
|
||||
|
||||
// Included from rules.kdl.
|
||||
window-rule {
|
||||
match app-id="Alacritty"
|
||||
open-maximized false
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="firefox$"
|
||||
open-maximized true
|
||||
}
|
||||
```
|
||||
|
||||
### Optional includes
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
By default, including a nonexistent file will cause an error.
|
||||
You can allow nonexistent includes by setting `optional=true`:
|
||||
|
||||
```kdl,must-fail
|
||||
// Won't fail if this file doesn't exist.
|
||||
include optional=true "optional-config.kdl"
|
||||
|
||||
// Regular include, will fail if the file doesn't exist.
|
||||
include "required-config.kdl"
|
||||
```
|
||||
|
||||
When an optional include file is missing, niri will emit a warning in the logs on every config reload.
|
||||
This reminds you that the file is missing while still loading the config successfully.
|
||||
|
||||
The optional file is still watched for changes, so if you create it later, the config will automatically reload and apply the new settings.
|
||||
|
||||
Note that `optional` only affects whether a missing file causes an error.
|
||||
If the file exists but contains invalid syntax or other errors, those errors will still cause a parsing failure.
|
||||
|
||||
|
||||
### Merging
|
||||
|
||||
Most config sections are merged between includes, meaning that you can set only a few properties, and only those properties will change.
|
||||
|
||||
```kdl
|
||||
// colors.kdl
|
||||
layout {
|
||||
// Does not affect gaps, border width, etc.
|
||||
// Only changes colors as written.
|
||||
focus-ring {
|
||||
active-color "blue"
|
||||
}
|
||||
|
||||
border {
|
||||
active-color "green"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
include "colors.kdl"
|
||||
|
||||
layout {
|
||||
// Does not set border and focus-ring colors,
|
||||
// so colors from colors.kdl are used.
|
||||
gaps 8
|
||||
|
||||
border {
|
||||
width 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Multipart sections
|
||||
|
||||
Multipart sections like `window-rule`, `output`, or `workspace` are inserted as is without merging:
|
||||
|
||||
```kdl
|
||||
// laptop.kdl
|
||||
output "eDP-1" {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
output "DP-2" {
|
||||
// ...
|
||||
}
|
||||
|
||||
include "laptop.kdl"
|
||||
|
||||
// End result: both DP-2 and eDP-1 settings.
|
||||
```
|
||||
|
||||
#### Binds
|
||||
|
||||
`binds` will override previously-defined conflicting keys:
|
||||
|
||||
```kdl
|
||||
// binds.kdl
|
||||
binds {
|
||||
Mod+T { spawn "alacritty"; }
|
||||
}
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
include "binds.kdl"
|
||||
|
||||
binds {
|
||||
// Overrides Mod+T from binds.kdl.
|
||||
Mod+T { spawn "foot"; }
|
||||
}
|
||||
```
|
||||
|
||||
#### Flags
|
||||
|
||||
Most flags can be disabled with `false`:
|
||||
|
||||
```kdl
|
||||
// csd.kdl
|
||||
|
||||
// Write "false" to explicitly disable.
|
||||
prefer-no-csd false
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
|
||||
// Enable prefer-no-csd in the main config.
|
||||
prefer-no-csd
|
||||
|
||||
// Including csd.kdl will disable it again.
|
||||
include "csd.kdl"
|
||||
```
|
||||
|
||||
#### Non-merging sections
|
||||
|
||||
Some sections where the contents represent a combined structure are not merged.
|
||||
Examples are `struts`, `preset-column-widths`, individual subsections in `animations`, pointing device sections in `input`.
|
||||
|
||||
```kdl
|
||||
// struts.kdl
|
||||
layout {
|
||||
struts {
|
||||
left 64
|
||||
right 64
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```kdl,must-fail
|
||||
// config.kdl
|
||||
layout {
|
||||
struts {
|
||||
top 64
|
||||
bottom 64
|
||||
}
|
||||
}
|
||||
|
||||
include "struts.kdl"
|
||||
|
||||
// Struts are not merged.
|
||||
// End result is only left and right struts.
|
||||
```
|
||||
|
||||
### Border special case
|
||||
|
||||
There's one special case that differs between the main config and included configs.
|
||||
|
||||
Writing `layout { border {} }` in an included config does nothing (since no properties are changed).
|
||||
However, writing the same in the main config will *enable* the border, i.e. it's equivalent to `layout { border { on; } }`.
|
||||
|
||||
So, if you want to move your layout configuration from the main config to a separate file, remember to add `on` to the border section, for example:
|
||||
|
||||
```kdl
|
||||
// separate.kdl
|
||||
layout {
|
||||
border {
|
||||
// Add this line:
|
||||
on
|
||||
|
||||
width 4
|
||||
active-color "#ffc87f"
|
||||
inactive-color "#505050"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The reason for this special case is that this is how it historically worked: back when I added borders, we didn't have any `on` flags, so I made writing the `border {}` section enable the border, with an explicit `off` to disable it.
|
||||
It wouldn't be too problematic to change it, however the default config always had a pre-filled `layout { border { off; } }` section with a note saying that commenting out the `off` is enough to enable the border.
|
||||
Many people likely have this part of the default config embedded in their configs now, so changing how it works would just cause a lot of confusion.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
In this section you can configure input devices like keyboard and mouse, and some input-related options.
|
||||
|
||||
There's a section for each device type: `keyboard`, `touchpad`, `mouse`, `trackpoint`, `tablet`, `touch`.
|
||||
There's a section for each device type: `keyboard`, `touchpad`, `mouse`, `trackpoint`, `trackball`, `tablet`, `touch`.
|
||||
Settings in those sections will apply to every device of that type.
|
||||
Currently, there's no way to configure specific devices individually (but that is planned).
|
||||
|
||||
@@ -89,6 +89,7 @@ input {
|
||||
tablet {
|
||||
// off
|
||||
map-to-output "eDP-1"
|
||||
// map-to-focused-output
|
||||
// left-handed
|
||||
// calibration-matrix 1.0 0.0 0.0 0.0 1.0 0.0
|
||||
}
|
||||
@@ -96,6 +97,7 @@ input {
|
||||
touch {
|
||||
// off
|
||||
map-to-output "eDP-1"
|
||||
// calibration-matrix 1.0 0.0 0.0 0.0 1.0 0.0
|
||||
}
|
||||
|
||||
// disable-power-key-handling
|
||||
@@ -256,9 +258,11 @@ Settings specific to `touchpad` and `mouse`:
|
||||
|
||||
<sup>Since: 25.08</sup> You can also override horizontal and vertical scroll factor separately like so: `scroll-factor horizontal=2.0 vertical=-1.0`
|
||||
|
||||
Settings specific to `tablet`s:
|
||||
Settings specific to `tablet` and `touch`:
|
||||
|
||||
- `calibration-matrix`: <sup>Since: 25.02</sup> set to six floating point numbers to change the calibration matrix. See the [`LIBINPUT_CALIBRATION_MATRIX` documentation](https://wayland.freedesktop.org/libinput/doc/latest/device-configuration-via-udev.html) for examples.
|
||||
- `calibration-matrix`: set to six floating point numbers to change the calibration matrix. See the [`LIBINPUT_CALIBRATION_MATRIX` documentation](https://wayland.freedesktop.org/libinput/doc/latest/device-configuration-via-udev.html) for examples.
|
||||
- <sup>Since: 25.02</sup> for `tablet`
|
||||
- <sup>Since: 25.11</sup> for `touch`
|
||||
|
||||
Tablets and touchscreens are absolute pointing devices that can be mapped to a specific output like so:
|
||||
|
||||
@@ -278,6 +282,10 @@ Valid output names are the same as the ones used for output configuration.
|
||||
|
||||
<sup>Since: 0.1.7</sup> When a tablet is not mapped to any output, it will map to the union of all connected outputs, without aspect ratio correction.
|
||||
|
||||
Setting specific to `tablet`:
|
||||
|
||||
- `map-to-focused-output`: <sup>Since: 26.04</sup> will map the tablet to the focused output, takes precedence over `map-to-output`.
|
||||
|
||||
### General Settings
|
||||
|
||||
These settings are not specific to a particular input device.
|
||||
|
||||
@@ -12,12 +12,14 @@ You can find documentation for various sections of the config on these wiki page
|
||||
* [`layer-rule {}`](./Configuration:-Layer-Rules.md)
|
||||
* [`animations {}`](./Configuration:-Animations.md)
|
||||
* [`gestures {}`](./Configuration:-Gestures.md)
|
||||
* [`recent-windows {}`](./Configuration:-Recent-Windows.md)
|
||||
* [`debug {}`](./Configuration:-Debug-Options.md)
|
||||
* [`include "other.kdl"`](./Configuration:-Include.md)
|
||||
|
||||
### Loading
|
||||
|
||||
Niri will load configuration from `$XDG_CONFIG_HOME/niri/config.kdl` or `~/.config/niri/config.kdl`, falling back to `/etc/niri/config.kdl`.
|
||||
If both of these files are missing, niri will create `$XDG_CONFIG_HOME/niri/config.kdl` with the contents of [the default configuration file](https://github.com/YaLTeR/niri/blob/main/resources/default-config.kdl), which are embedded into the niri binary at build time.
|
||||
If both of these files are missing, niri will create `$XDG_CONFIG_HOME/niri/config.kdl` with the contents of [the default configuration file](https://github.com/niri-wm/niri/blob/main/resources/default-config.kdl), which are embedded into the niri binary at build time.
|
||||
Please use the default configuration file as the starting point for your custom configuration.
|
||||
|
||||
The configuration is live-reloaded.
|
||||
|
||||
@@ -354,7 +354,7 @@ binds {
|
||||
Actions for taking screenshots.
|
||||
|
||||
- `screenshot`: opens the built-in interactive screenshot UI.
|
||||
- `screenshot-screen`, `screenshow-window`: takes a screenshot of the focused screen or window respectively.
|
||||
- `screenshot-screen`, `screenshot-window`: takes a screenshot of the focused screen or window respectively.
|
||||
|
||||
The screenshot is both stored to the clipboard and saved to disk, according to the [`screenshot-path` option](./Configuration:-Miscellaneous.md#screenshot-path).
|
||||
|
||||
@@ -382,6 +382,17 @@ binds {
|
||||
}
|
||||
```
|
||||
|
||||
<sup>Since: 26.04</sup> You can show the mouse pointer on window screenshots with the `show-pointer=true` property.
|
||||
The pointer will be included only if the window is currently receiving pointer input (usually this means the pointer is on top of the window).
|
||||
|
||||
```kdl
|
||||
binds {
|
||||
// The pointer will be visible on the screenshot
|
||||
// if it's on top of the window.
|
||||
Alt+Print { screenshot-window show-pointer=true; }
|
||||
}
|
||||
```
|
||||
|
||||
#### `toggle-keyboard-shortcuts-inhibit`
|
||||
|
||||
<sup>Since: 25.02</sup>
|
||||
|
||||
@@ -14,6 +14,7 @@ Here are all matchers and properties that a layer rule could have:
|
||||
layer-rule {
|
||||
match namespace="waybar"
|
||||
match at-startup=true
|
||||
match layer="top"
|
||||
|
||||
// Properties that apply continuously.
|
||||
opacity 0.5
|
||||
@@ -34,6 +35,25 @@ layer-rule {
|
||||
geometry-corner-radius 12
|
||||
place-within-backdrop true
|
||||
baba-is-float true
|
||||
|
||||
background-effect {
|
||||
xray true
|
||||
blur true
|
||||
noise 0.05
|
||||
saturation 3
|
||||
}
|
||||
|
||||
popups {
|
||||
opacity 0.5
|
||||
geometry-corner-radius 6
|
||||
|
||||
background-effect {
|
||||
xray true
|
||||
blur true
|
||||
noise 0.05
|
||||
saturation 3
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -69,6 +89,22 @@ layer-rule {
|
||||
}
|
||||
```
|
||||
|
||||
#### `layer`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Matches surfaces on this layer-shell layer.
|
||||
Can be `"background"`, `"bottom"`, `"top"`, or `"overlay"`.
|
||||
|
||||
```kdl
|
||||
// Make all overlay-layer surfaces FLOAT.
|
||||
layer-rule {
|
||||
match layer="overlay"
|
||||
|
||||
baba-is-float true
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Properties
|
||||
|
||||
These properties apply continuously to open layer-shell surfaces.
|
||||
@@ -191,3 +227,68 @@ layer-rule {
|
||||
baba-is-float true
|
||||
}
|
||||
```
|
||||
|
||||
#### `background-effect`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Override the background effect options for this surface.
|
||||
|
||||
- `xray`: set to `true` to enable the xray effect, or `false` to disable it.
|
||||
- `blur`: set to `true` to enable blur behind this surface, or `false` to force-disable it.
|
||||
- `noise`: amount of pixel noise added to the background (helps with color banding from blur).
|
||||
- `saturation`: color saturation of the background (`0` is desaturated, `1` is normal, `2` is 200% saturation).
|
||||
|
||||
See the [window effects page](./Window-Effects.md) for an overview of background effects.
|
||||
|
||||
```kdl
|
||||
// Make top and overlay layers use the regular blur (if enabled),
|
||||
// while bottom and background layers keep using the efficient xray blur.
|
||||
layer-rule {
|
||||
match layer="top"
|
||||
match layer="overlay"
|
||||
|
||||
background-effect {
|
||||
xray false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `popups`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Override properties for this layer surface's pop-ups (e.g. a menu opened by clicking an item in Waybar).
|
||||
|
||||
The properties work the same way as the corresponding layer-rule properties, except that they apply to the layer surface's pop-ups rather than to the layer surface itself.
|
||||
|
||||
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
|
||||
Other properties apply independently.
|
||||
|
||||
> [!NOTE]
|
||||
> This block affects only pop-ups created by the app via Wayland's [xdg-popup](https://wayland.app/protocols/xdg-shell#xdg_popup) (which should be most of them).
|
||||
>
|
||||
> Some desktop shells will emulate pop-ups by drawing something that looks like a pop-up inside a regular layer surface.
|
||||
> As far as niri is concerned, those are just layer surfaces and not pop-ups, so this block won't apply to them.
|
||||
>
|
||||
> This block also does not affect input-method pop-ups, such as Fcitx.
|
||||
|
||||
```kdl
|
||||
// Blur the background behind Waybar popup menus.
|
||||
layer-rule {
|
||||
match namespace="^waybar$"
|
||||
|
||||
popups {
|
||||
// Match the default GTK 3 popup corner radius.
|
||||
geometry-corner-radius 6
|
||||
opacity 0.85
|
||||
|
||||
background-effect {
|
||||
blur true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keep in mind that the background effect will look right only if the pop-up is shaped like a (rounded) rectangle, and the layer surface correctly sets its Wayland geometry to exclude any shadows.
|
||||
Pop-ups with custom shapes will need the app to implement the [ext-background-effect protocol](https://wayland.app/protocols/ext-background-effect-v1) to work properly.
|
||||
|
||||
@@ -29,6 +29,7 @@ layout {
|
||||
|
||||
focus-ring {
|
||||
// off
|
||||
on
|
||||
width 4
|
||||
active-color "#7fc8ff"
|
||||
inactive-color "#505050"
|
||||
@@ -40,6 +41,7 @@ layout {
|
||||
|
||||
border {
|
||||
off
|
||||
// on
|
||||
width 4
|
||||
active-color "#ffc87f"
|
||||
inactive-color "#505050"
|
||||
@@ -50,6 +52,7 @@ layout {
|
||||
}
|
||||
|
||||
shadow {
|
||||
off
|
||||
// on
|
||||
softness 30
|
||||
spread 5
|
||||
@@ -61,6 +64,7 @@ layout {
|
||||
|
||||
tab-indicator {
|
||||
// off
|
||||
on
|
||||
hide-when-single-tab
|
||||
place-within-column
|
||||
gap 5
|
||||
@@ -79,6 +83,7 @@ layout {
|
||||
|
||||
insert-hint {
|
||||
// off
|
||||
on
|
||||
color "#ffc87f80"
|
||||
// gradient from="#ffbb6680" to="#ffc88080" angle=45 relative-to="workspace-view"
|
||||
}
|
||||
@@ -92,6 +97,8 @@ layout {
|
||||
}
|
||||
```
|
||||
|
||||
<sup>Since: 25.11</sup> You can override these settings for specific [outputs](./Configuration:-Outputs.md#layout-config-overrides) and [named workspaces](./Configuration:-Named-Workspaces.md#layout-config-overrides).
|
||||
|
||||
### `gaps`
|
||||
|
||||
Set gaps around (inside and outside) windows in logical pixels.
|
||||
@@ -170,6 +177,7 @@ layout {
|
||||
### `preset-column-widths`
|
||||
|
||||
Set the widths that the `switch-preset-column-width` action (Mod+R) toggles between.
|
||||
<sup>Since: 25.08</sup> You can use the `switch-preset-column-width-back` action (Mod+Shift+R) to toggle in reverse.
|
||||
|
||||
`proportion` sets the width as a fraction of the output width, taking gaps into account.
|
||||
For example, you can perfectly fit four windows sized `proportion 0.25` on an output, regardless of the gaps setting.
|
||||
@@ -221,7 +229,8 @@ layout {
|
||||
|
||||
<sup>Since: 0.1.9</sup>
|
||||
|
||||
Set the heights that the `switch-preset-window-height` action (Mod+Shift+R) toggles between.
|
||||
Set the heights that the `switch-preset-window-height` action (Mod+Ctrl+Shift+R) toggles between.
|
||||
<sup>Since: 25.08</sup> You can use the `switch-preset-window-height-back` action (not bound by default) to toggle in reverse.
|
||||
|
||||
`proportion` sets the height as a fraction of the output height, taking gaps into account.
|
||||
The default preset heights are <sup>1</sup>⁄<sub>3</sub>, <sup>1</sup>⁄<sub>2</sub> and <sup>2</sup>⁄<sub>3</sub> of the output.
|
||||
@@ -454,6 +463,7 @@ When `gaps-between-tabs` is zero, only the first and the last tabs have rounded
|
||||
They have the same semantics as the border and focus ring colors and gradients.
|
||||
|
||||
Tab colors are picked in this order:
|
||||
|
||||
1. Colors from the `tab-indicator` window rule, if set.
|
||||
1. Colors from the `tab-indicator` layout options, if set (you're here).
|
||||
1. If neither are set, niri picks the color matching the window border or focus ring, whichever one is active.
|
||||
@@ -550,4 +560,4 @@ layout {
|
||||
}
|
||||
```
|
||||
|
||||
You can also set the color per-output [in the output config](./Configuration:-Outputs.md#background-color).
|
||||
You can also set the color per-output [in the output config](./Configuration:-Outputs.md#layout-config-overrides).
|
||||
|
||||
@@ -54,6 +54,14 @@ hotkey-overlay {
|
||||
config-notification {
|
||||
disable-failed
|
||||
}
|
||||
|
||||
blur {
|
||||
// off
|
||||
passes 3
|
||||
offset 3.0
|
||||
noise 0.02
|
||||
saturation 1.5
|
||||
}
|
||||
```
|
||||
|
||||
### `spawn-at-startup`
|
||||
@@ -141,6 +149,13 @@ environment {
|
||||
}
|
||||
```
|
||||
|
||||
Note that these variables do not propagate to the systemd global environment, so tools and applications started by systemd do not see them.
|
||||
In particular, if you start a desktop shell like DankMaterialShell through systemd, then use its built-in application launcher, the apps won't see these environment variables.
|
||||
|
||||
If you want all processes to see the environment variables, you can set them in your login shell config instead (i.e. `~/.bash_profile`).
|
||||
The `niri-session` shell script runs through the login shell and imports all environment variables to systemd before starting niri.
|
||||
Keep in mind that all compositors will see variables set in the login shell, not just niri.
|
||||
|
||||
### `cursor`
|
||||
|
||||
Change the theme and size of the cursor as well as set the `XCURSOR_THEME` and `XCURSOR_SIZE` environment variables.
|
||||
@@ -313,3 +328,82 @@ config-notification {
|
||||
disable-failed
|
||||
}
|
||||
```
|
||||
|
||||
### `blur`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Blur configuration that affects all background blur.
|
||||
|
||||
See the [window effects page](./Window-Effects.md) for an overview of background effects.
|
||||
|
||||
```kdl
|
||||
// These are the default values:
|
||||
blur {
|
||||
// off
|
||||
passes 3
|
||||
offset 3
|
||||
noise 0.02
|
||||
saturation 1.5
|
||||
}
|
||||
```
|
||||
|
||||
#### `off`
|
||||
|
||||
By default, blur is available on request by a window or layer surface (via the `ext-background-effect` protocol).
|
||||
You can also enable it manually with the `blur true` background effect [window](./Configuration:-Window-Rules.md#background-effect) or [layer](./Configuration:-Layer-Rules.md#background-effect) rule.
|
||||
|
||||
Setting the `off` flag will disable all blur, both requested by the window, and configured in window rules.
|
||||
|
||||
```kdl
|
||||
blur {
|
||||
off
|
||||
}
|
||||
```
|
||||
|
||||
#### `passes` and `offset`
|
||||
|
||||
`passes` controls the number of downsample/upsample passes for dual kawase blur.
|
||||
More passes produce a larger, smoother blur, but cost more GPU resources.
|
||||
|
||||
`offset` is the pixel offset multiplier for each pass.
|
||||
Offset `1` is the original dual kawase blur.
|
||||
Larger values produce a smoother blur, at no additional GPU cost.
|
||||
|
||||
However, setting `offset` too big will produce visual artifacts.
|
||||
You will need to increase `passes` to be able to use a bigger `offset` without artifacts.
|
||||
|
||||
When configuring blur, try increasing `offset` first (since it doesn't cause any extra GPU load) until you start getting artifacts.
|
||||
Then, if you still need smoother blur, increase `passes` by 1.
|
||||
Keep doing this until you get the desired visuals.
|
||||
|
||||
```kdl
|
||||
blur {
|
||||
passes 3
|
||||
offset 3.0
|
||||
}
|
||||
```
|
||||
|
||||
#### `noise`
|
||||
|
||||
Amount of noise to add on top of the blur.
|
||||
|
||||
This is helpful to reduce color banding artifacts.
|
||||
|
||||
```kdl
|
||||
blur {
|
||||
noise 0.02
|
||||
}
|
||||
```
|
||||
|
||||
#### `saturation`
|
||||
|
||||
Color saturation applied to the blurred background.
|
||||
|
||||
Values above `1` increase saturation; values below `1` reduce it.
|
||||
|
||||
```kdl
|
||||
blur {
|
||||
saturation 1.5
|
||||
}
|
||||
```
|
||||
|
||||
@@ -45,3 +45,53 @@ Before, it could only use the connector name.
|
||||
<sup>Since: 25.02</sup> Named workspaces no longer update/forget their original output when opening a new window on them (unnamed workspaces will keep doing that).
|
||||
This means that named workspaces "stick" to their original output in more cases, reflecting their more permanent nature.
|
||||
Explicitly moving a named workspace to a different monitor will still update its original output.
|
||||
|
||||
### Layout config overrides
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
You can customize layout settings for named workspaces with a `layout {}` block:
|
||||
|
||||
```kdl
|
||||
workspace "aesthetic" {
|
||||
// Layout config overrides just for this named workspace.
|
||||
layout {
|
||||
gaps 32
|
||||
|
||||
struts {
|
||||
left 64
|
||||
right 64
|
||||
bottom 64
|
||||
top 64
|
||||
}
|
||||
|
||||
border {
|
||||
on
|
||||
width 4
|
||||
}
|
||||
|
||||
// ...any other setting.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It accepts all the same options as [the top-level `layout {}` block](./Configuration:-Layout.md), except:
|
||||
|
||||
- `empty-workspace-above-first`: this is an output-level setting, doesn't make sense on a workspace.
|
||||
- `insert-hint`: currently we always draw these at the output level, so it's not customizable per-workspace.
|
||||
|
||||
In order to unset a flag, write it with `false`, e.g.:
|
||||
|
||||
```kdl
|
||||
layout {
|
||||
// Enabled globally.
|
||||
always-center-single-column
|
||||
}
|
||||
|
||||
workspace "uncentered" {
|
||||
layout {
|
||||
// Unset on this workspace.
|
||||
always-center-single-column false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -14,8 +14,23 @@ output "eDP-1" {
|
||||
position x=1280 y=0
|
||||
variable-refresh-rate // on-demand=true
|
||||
focus-at-startup
|
||||
background-color "#003300"
|
||||
backdrop-color "#001100"
|
||||
|
||||
hot-corners {
|
||||
// off
|
||||
top-left
|
||||
// top-right
|
||||
// bottom-left
|
||||
// bottom-right
|
||||
}
|
||||
|
||||
layout {
|
||||
// ...layout settings for eDP-1...
|
||||
}
|
||||
|
||||
// Custom modes. Caution: may damage your display.
|
||||
// mode custom=true "1920x1080@100"
|
||||
// modeline 173.00 1920 2048 2248 2576 1080 1083 1088 1120 "-hsync" "+vsync"
|
||||
}
|
||||
|
||||
output "HDMI-A-1" {
|
||||
@@ -75,6 +90,50 @@ output "eDP-1" {
|
||||
}
|
||||
```
|
||||
|
||||
#### `mode custom=true`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
You can configure a custom mode (not offered by the monitor) by setting `custom=true`.
|
||||
In this case, the refresh rate is mandatory.
|
||||
|
||||
Custom modes are not guaranteed to work.
|
||||
Niri is asking the monitor to run in a mode that is not supported by the manufacturer.
|
||||
Use at your own risk.
|
||||
|
||||
> [!CAUTION]
|
||||
> Custom modes may damage your monitor, especially if it's a CRT.
|
||||
> Follow the maximum supported limits in your monitor's instructions.
|
||||
|
||||
```kdl
|
||||
// Use a custom mode for this display.
|
||||
output "HDMI-A-1" {
|
||||
mode custom=true "2560x1440@143.912"
|
||||
}
|
||||
```
|
||||
|
||||
### `modeline`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
Directly configures the monitor's mode via a modeline, overriding any configured `mode`.
|
||||
The modeline can be calculated via utilities such as [cvt](https://man.archlinux.org/man/cvt.1.en) or [gtf](https://man.archlinux.org/man/gtf.1.en).
|
||||
|
||||
Modelines are not guaranteed to work.
|
||||
Niri is asking the monitor to run in a mode not supported by the manufacturer.
|
||||
Use at your own risk.
|
||||
|
||||
> [!CAUTION]
|
||||
> Out of spec modelines may damage your monitor, especially if it's a CRT.
|
||||
> Follow the maximum supported limits in your monitor's instructions.
|
||||
|
||||
```kdl
|
||||
// Use a modeline for this display.
|
||||
output "eDP-3" {
|
||||
modeline 173.00 1920 2048 2248 2576 1080 1083 1088 1120 "-hsync" "+vsync"
|
||||
}
|
||||
```
|
||||
|
||||
### `scale`
|
||||
|
||||
Set the scale of the monitor.
|
||||
@@ -197,6 +256,8 @@ This is visible when you're not using any background tools like swaybg.
|
||||
|
||||
<sup>Until: 25.05</sup> The alpha channel for this color will be ignored.
|
||||
|
||||
<sup>Since: 25.11</sup> This setting is deprecated, set `background-color` in the [output `layout {}` block](#layout-config-overrides) instead.
|
||||
|
||||
```kdl
|
||||
output "HDMI-A-1" {
|
||||
background-color "#003300"
|
||||
@@ -217,3 +278,83 @@ output "HDMI-A-1" {
|
||||
backdrop-color "#001100"
|
||||
}
|
||||
```
|
||||
|
||||
### `hot-corners`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
Customize the hot corners for this output.
|
||||
By default, hot corners [in the gestures settings](./Configuration:-Gestures.md#hot-corners) are used for all outputs.
|
||||
|
||||
Hot corners toggle the overview when you put your mouse at the very corner of a monitor.
|
||||
|
||||
`off` will disable the hot corners on this output, and writing specific corners will enable only those hot corners on this output.
|
||||
|
||||
```kdl
|
||||
// Enable the bottom-left and bottom-right hot corners on HDMI-A-1.
|
||||
output "HDMI-A-1" {
|
||||
hot-corners {
|
||||
bottom-left
|
||||
bottom-right
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the hot corners on DP-2.
|
||||
output "DP-2" {
|
||||
hot-corners {
|
||||
off
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Layout config overrides
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
You can customize layout settings for an output with a `layout {}` block:
|
||||
|
||||
```kdl
|
||||
output "SomeCompany VerticalMonitor 1234" {
|
||||
transform "90"
|
||||
|
||||
// Layout config overrides just for this output.
|
||||
layout {
|
||||
default-column-width { proportion 1.0; }
|
||||
|
||||
// ...any other setting.
|
||||
}
|
||||
}
|
||||
|
||||
output "SomeCompany UltrawideMonitor 1234" {
|
||||
// Narrower proportions and more presets for an ultrawide.
|
||||
layout {
|
||||
default-column-width { proportion 0.25; }
|
||||
|
||||
preset-column-widths {
|
||||
proportion 0.2
|
||||
proportion 0.25
|
||||
proportion 0.5
|
||||
proportion 0.75
|
||||
proportion 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It accepts all the same options as [the top-level `layout {}` block](./Configuration:-Layout.md).
|
||||
|
||||
In order to unset a flag, write it with `false`, e.g.:
|
||||
|
||||
```kdl
|
||||
layout {
|
||||
// Enabled globally.
|
||||
always-center-single-column
|
||||
}
|
||||
|
||||
output "eDP-1" {
|
||||
layout {
|
||||
// Unset on this output.
|
||||
always-center-single-column false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
### Overview
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
In this section you can configure the recent windows switcher (Alt-Tab).
|
||||
|
||||
Here is an outline of the available settings and their default values:
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// off
|
||||
debounce-ms 750
|
||||
|
||||
open-delay-ms 150
|
||||
|
||||
highlight {
|
||||
active-color "#999999ff"
|
||||
urgent-color "#ff9999ff"
|
||||
padding 30
|
||||
corner-radius 0
|
||||
}
|
||||
|
||||
previews {
|
||||
max-height 480
|
||||
max-scale 0.5
|
||||
}
|
||||
|
||||
binds {
|
||||
Alt+Tab { next-window; }
|
||||
Alt+Shift+Tab { previous-window; }
|
||||
Alt+grave { next-window filter="app-id"; }
|
||||
Alt+Shift+grave { previous-window filter="app-id"; }
|
||||
|
||||
Mod+Tab { next-window; }
|
||||
Mod+Shift+Tab { previous-window; }
|
||||
Mod+grave { next-window filter="app-id"; }
|
||||
Mod+Shift+grave { previous-window filter="app-id"; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`off` disables the recent windows switcher altogether.
|
||||
|
||||
### `debounce-ms`
|
||||
|
||||
Delay, in milliseconds, between the window receiving focus and getting "committed" to the recent windows list.
|
||||
|
||||
When you want to focus some window, you might end up focusing some unrelated windows on the way:
|
||||
|
||||
- with keyboard navigation, the windows between your current one and the target one;
|
||||
- with [`focus-follows-mouse`](./Configuration:-Input.md#focus-follows-mouse), the windows you happen to cross with the mouse pointer on the way to the target window.
|
||||
|
||||
The debounce delay prevents those intermediate windows from polluting the recent windows list.
|
||||
|
||||
Note that some actions, like keyboard input into the target window, will skip this delay and commit the window to the list immediately.
|
||||
This way, the recent windows list stays responsive while not getting polluted too much with unintended windows.
|
||||
|
||||
If you want windows to appear in recent windows right away, including intermediate windows, you can reduce the delay or set it to zero:
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Commit windows to the recent windows list as soon as they're focused,
|
||||
// with no debounce delay.
|
||||
debounce-ms 0
|
||||
}
|
||||
```
|
||||
|
||||
### `open-delay-ms`
|
||||
|
||||
Delay, in milliseconds, between pressing the Alt-Tab bind and the recent windows switcher visually appearing on screen.
|
||||
|
||||
The switcher is delayed by default so that quickly tapping Alt-Tab to switch windows wouldn't cause annoying fullscreen visual changes.
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Make the switcher appear instantly.
|
||||
open-delay-ms 0
|
||||
}
|
||||
```
|
||||
|
||||
### `highlight`
|
||||
|
||||
Controls the highlight behind the focused window preview in the recent windows switcher.
|
||||
|
||||
- `active-color`: normal color of the focused window highlight.
|
||||
- `urgent-color`: color of an urgent focused window highlight, also visible in a darker shade on unfocused windows.
|
||||
- `padding`: padding of the highlight around the window preview, in logical pixels.
|
||||
- `corner-radius`: corner radius of the highlight.
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Round the corners on the highlight.
|
||||
highlight {
|
||||
corner-radius 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `previews`
|
||||
|
||||
Controls the window previews in the switcher.
|
||||
|
||||
- `max-scale`: maximum scale of the window previews.
|
||||
Windows cannot be scaled bigger than this value.
|
||||
- `max-height`: maximum height of the window previews.
|
||||
Further limits the size of the previews in order to occupy less space on large monitors.
|
||||
|
||||
On smaller monitors, the previews will be primarily limited by `max-scale`, and on larger monitors they will be primarily limited by `max-height`.
|
||||
|
||||
The `max-scale` limit is imposed twice: on the final window scale, and on the window height which cannot exceed `monitor height × max scale`.
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Make the previews smaller to fit more on screen.
|
||||
previews {
|
||||
max-height 320
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Make the previews larger to see the window contents.
|
||||
previews {
|
||||
max-height 1080
|
||||
max-scale 0.75
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `binds`
|
||||
|
||||
Configure binds that open and navigate the recent windows switcher.
|
||||
|
||||
The defaults are <kbd>Alt</kbd><kbd>Tab</kbd> / <kbd>Mod</kbd><kbd>Tab</kbd> to switch across all windows, and <kbd>Alt</kbd><kbd>\`</kbd> / <kbd>Mod</kbd><kbd>\`</kbd> to switch between windows of the current application.
|
||||
Adding <kbd>Shift</kbd> will switch windows backwards.
|
||||
|
||||
Adding the recent windows `binds {}` section to your config removes all default binds.
|
||||
You can copy the ones you need from the summary at the top of this wiki page.
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Even an empty binds {} section will remove all default binds.
|
||||
binds {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The available actions are `next-window` and `previous-window`.
|
||||
They can optionally have the following properties:
|
||||
|
||||
- `filter="app-id"`: filters the switcher to the windows of the currently selected application, as determined by the Wayland app ID.
|
||||
- `scope="all"`, `scope="output"`, `scope="workspace"`: sets the pre-selected scope when this bind is used to open the recent windows switcher.
|
||||
|
||||
```kdl
|
||||
recent-windows {
|
||||
// Pre-select the "Output" scope when switching windows.
|
||||
binds {
|
||||
Mod+Tab { next-window scope="output"; }
|
||||
Mod+Shift+Tab { previous-window scope="output"; }
|
||||
Mod+grave { next-window scope="output" filter="app-id"; }
|
||||
Mod+Shift+grave { previous-window scope="output" filter="app-id"; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The recent windows binds have lower precedence than the [normal binds](./Configuration:-Key-Bindings.md), meaning that if you have <kbd>Alt</kbd><kbd>Tab</kbd> bound to something else in the normal binds, the `recent-windows` bind won't work.
|
||||
In this case, you can remove the conflicting normal bind.
|
||||
|
||||
All binds in this section must have a modifier key like <kbd>Alt</kbd> or <kbd>Mod</kbd> because the recent windows switcher remains open only while you hold any modifier key.
|
||||
|
||||
#### Bindings inside the switcher
|
||||
|
||||
When the switcher is open, some hardcoded binds are available:
|
||||
|
||||
- <kbd>Escape</kbd> cancels the switcher.
|
||||
- <kbd>Enter</kbd> closes the switcher confirming the current window.
|
||||
- <kbd>A</kbd>, <kbd>W</kbd>, <kbd>O</kbd> select a specific scope.
|
||||
- <kbd>S</kbd> cycles between scopes, as indicated by the panel at the top.
|
||||
- <kbd>←</kbd>, <kbd>→</kbd>, <kbd>Home</kbd>, <kbd>End</kbd> move the selection directionally.
|
||||
|
||||
Additionally, certain regular binds will automatically work in the switcher:
|
||||
|
||||
- focus column left/right and their variants: will move the selection left/right inside the switcher.
|
||||
- focus column first/last: will move the selection to the first or last window.
|
||||
- close window: will close the window currently focused in the switcher.
|
||||
- screenshot: will open the screenshot UI.
|
||||
|
||||
The way this works is by finding all regular binds corresponding to these actions and taking just the trigger key without modifiers.
|
||||
For example, if you have <kbd>Mod</kbd><kbd>Shift</kbd><kbd>C</kbd> bound to `close-window`, in the window switcher pressing <kbd>C</kbd> on its own will close the window.
|
||||
|
||||
This way we don't need to hardcode things like HJKL directional movements.
|
||||
If you have, say, Colemak-DH MNEI binds instead, they will work for you in the window switcher (as long as they don't conflict with the hardcoded ones).
|
||||
@@ -39,6 +39,9 @@ switch-events {
|
||||
These events trigger when a convertible laptop goes into or out of tablet mode.
|
||||
In tablet mode, the keyboard and mouse are usually inaccessible, so you can use these events to activate the on-screen keyboard.
|
||||
|
||||
> [!NOTE]
|
||||
> The commands below are just examples, you will need to provide your own on-screen keyboard, such as [sysboard](https://github.com/System64fumo/sysboard) or [wvkbd](https://github.com/jjsullivan5196/wvkbd).
|
||||
|
||||
```kdl
|
||||
switch-events {
|
||||
tablet-mode-on { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true"; }
|
||||
|
||||
@@ -44,6 +44,7 @@ window-rule {
|
||||
open-on-output "Some Company CoolMonitor 1234"
|
||||
open-on-workspace "chat"
|
||||
open-maximized true
|
||||
open-maximized-to-edges true
|
||||
open-fullscreen true
|
||||
open-floating true
|
||||
open-focused false
|
||||
@@ -99,6 +100,25 @@ window-rule {
|
||||
tiled-state true
|
||||
baba-is-float true
|
||||
|
||||
background-effect {
|
||||
xray true
|
||||
blur true
|
||||
noise 0.05
|
||||
saturation 3
|
||||
}
|
||||
|
||||
popups {
|
||||
opacity 0.5
|
||||
geometry-corner-radius 15
|
||||
|
||||
background-effect {
|
||||
xray true
|
||||
blur true
|
||||
noise 0.05
|
||||
saturation 3
|
||||
}
|
||||
}
|
||||
|
||||
min-width 100
|
||||
max-width 200
|
||||
min-height 300
|
||||
@@ -172,7 +192,7 @@ window-rule {
|
||||
}
|
||||
```
|
||||
|
||||
You can find the title and the app ID of the currently focused window by running `niri msg focused-window`.
|
||||
You can find the title and the app ID of a window by running `niri msg pick-window` and clicking on the window in question.
|
||||
|
||||
> [!TIP]
|
||||
> Another way to find the window title and app ID is to configure the `wlr/taskbar` module in [Waybar](https://github.com/Alexays/Waybar) to include them in the tooltip:
|
||||
@@ -416,9 +436,29 @@ window-rule {
|
||||
}
|
||||
```
|
||||
|
||||
#### `open-maximized-to-edges`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
Make the window open [maximized to edges](./Fullscreen-and-Maximize.md).
|
||||
|
||||
```kdl
|
||||
window-rule {
|
||||
open-maximized-to-edges true
|
||||
}
|
||||
```
|
||||
|
||||
You can also set this to `false` to *prevent* a window from opening maximized to edges.
|
||||
|
||||
```kdl
|
||||
window-rule {
|
||||
open-maximized-to-edges false
|
||||
}
|
||||
```
|
||||
|
||||
#### `open-fullscreen`
|
||||
|
||||
Make the window open fullscreen.
|
||||
Make the window open [fullscreen](./Fullscreen-and-Maximize.md).
|
||||
|
||||
```kdl
|
||||
window-rule {
|
||||
@@ -602,6 +642,7 @@ window-rule {
|
||||
<sup>Since: 25.02</sup>
|
||||
|
||||
Set the default display mode for columns created from this window.
|
||||
Can be `normal` or `tabbed`.
|
||||
|
||||
This is used any time a window goes into its own column.
|
||||
For example:
|
||||
@@ -887,6 +928,95 @@ https://github.com/user-attachments/assets/3f4cb1a4-40b2-4766-98b7-eec014c19509
|
||||
|
||||
</video>
|
||||
|
||||
#### `background-effect`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Override the background effect options for this window.
|
||||
|
||||
- `xray`: set to `true` to enable the xray effect, or `false` to disable it.
|
||||
- `blur`: set to `true` to enable blur behind this window, or `false` to force-disable it.
|
||||
- `noise`: amount of pixel noise added to the background (helps with color banding from blur).
|
||||
- `saturation`: color saturation of the background (`0` is desaturated, `1` is normal, `2` is 200% saturation).
|
||||
|
||||
See the [window effects page](./Window-Effects.md) for an overview of background effects.
|
||||
|
||||
```kdl
|
||||
// Make floating windows use the regular blur (if enabled),
|
||||
// while tiled windows keep using the efficient xray blur.
|
||||
//
|
||||
// Warning: non-xray blur is currently experimental and has known limitations.
|
||||
// In particular, it doesn't work during window opening and closing animations.
|
||||
window-rule {
|
||||
match is-floating=true
|
||||
|
||||
background-effect {
|
||||
xray false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `popups`
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
Override properties for this window's pop-ups (menus and tooltips).
|
||||
|
||||
The properties work the same way as the corresponding window-rule properties, except that they apply to the window's pop-ups rather than to the window itself.
|
||||
|
||||
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
|
||||
Other properties apply independently.
|
||||
|
||||
> [!NOTE]
|
||||
> This block affects only pop-ups created by the app via Wayland's [xdg-popup](https://wayland.app/protocols/xdg-shell#xdg_popup) (which should be most of them).
|
||||
>
|
||||
> Examples of things that look like pop-ups that won't work:
|
||||
>
|
||||
> - Fully emulated by the client, i.e. not a pop-up at all, the client just draws something that looks like a pop-up inside its window.
|
||||
> These are common in game engines and in web apps, e.g. the right click menu in Google Docs or in Electron apps like Discord.
|
||||
>
|
||||
> - Uses a wl-subsurface instead of an xdg-popup.
|
||||
> Common in older apps using GTK 3, notably Firefox still uses these for some menus.
|
||||
> Subsurfaces are an indivisible part of a surface and they aren't usually pop-ups, so it wouldn't make sense for niri to apply these rules to them.
|
||||
>
|
||||
> These emulated pop-ups come with other downsides: they cannot reliably extend outside their window, and if the app tries to do that, they will be clipped by rules such as `clip-to-geometry`.
|
||||
> So most modern apps will correctly use xdg-popup, which is the intended way to show pop-ups on Wayland.
|
||||
>
|
||||
> This block also does not affect input-method pop-ups, such as Fcitx.
|
||||
>
|
||||
> For pop-ups created by your desktop shell or desktop components, use the corresponding [layer rule](./Configuration:-Layer-Rules.md#popups).
|
||||
|
||||
```kdl
|
||||
// Blur the background behind pop-up menus in Nautilus.
|
||||
window-rule {
|
||||
match app-id="Nautilus"
|
||||
|
||||
popups {
|
||||
// Matches the default libadwaita pop-up corner radius.
|
||||
geometry-corner-radius 15
|
||||
|
||||
// Note: it'll look better to set background opacity
|
||||
// through your GTK theme CSS and not here.
|
||||
// This is just an example that makes it look obvious.
|
||||
opacity 0.5
|
||||
|
||||
background-effect {
|
||||
blur true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keep in mind that the background effect will look right only if the pop-up is shaped like a (rounded) rectangle, and the window correctly sets its Wayland geometry to exclude any shadows.
|
||||
For example, GTK 4 pop-ups with pointing arrows (`has-arrow=true` property) are *not* rounded rectangles—the arrow sticks out—so if you enable blur, it will also stick out of the pop-up.
|
||||
|
||||
| Correct | Wrong |
|
||||
|-----------------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| The pop-up is a rounded rectangle. Blur looks fine. | The pop-up is not a rounded rectangle. Blur extends above, where the arrow is. |
|
||||
|  |  |
|
||||
|
||||
These pop-ups with custom shapes will need the app to implement the [ext-background-effect protocol](https://wayland.app/protocols/ext-background-effect-v1) to work properly.
|
||||
|
||||
#### Size Overrides
|
||||
|
||||
You can amend the window's minimum and maximum size in logical pixels.
|
||||
|
||||
@@ -74,7 +74,7 @@ Here are some design considerations for the window layout logic.
|
||||
|
||||
## Default config
|
||||
|
||||
The [default config](https://github.com/YaLTeR/niri/blob/main/resources/default-config.kdl) is intended to give a familiar, helpful, and not too jarring experience to new niri users.
|
||||
The [default config](https://github.com/niri-wm/niri/blob/main/resources/default-config.kdl) is intended to give a familiar, helpful, and not too jarring experience to new niri users.
|
||||
Importantly, it is not a "suggested rice config"; we don't want to startle people with full-on rainbow borders and crazy shaders.
|
||||
|
||||
Since we're not a complete desktop environment (and don't have the contributor base to become one), we cannot provide a fully integrated experience—distro spins are better positioned to do this.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
niri's documentation files are found in `docs/wiki/` and should be viewable and browsable in at least three systems:
|
||||
|
||||
- The GitHub repo's markdown file preview
|
||||
- [The GitHub repo's wiki](https://github.com/YaLTeR/niri/wiki)
|
||||
- [The documentation site](https://yalter.github.io/niri/)
|
||||
- [The GitHub repo's wiki](https://github.com/niri-wm/niri/wiki)
|
||||
- [The documentation site](https://niri-wm.github.io/niri/)
|
||||
|
||||
## The GitHub repo's wiki
|
||||
|
||||
|
||||
+83
-7
@@ -40,12 +40,26 @@ hotkey-overlay {
|
||||
}
|
||||
```
|
||||
|
||||
### How to fix lag on external monitors connected to a hybrid GPU laptop?
|
||||
|
||||
Hybrid GPU laptops (which have both an integrated and a discrete GPU) generally connect the external monitor port to the discrete GPU.
|
||||
Meanwhile, the built-in monitor is connected to the integrated GPU, and the integrated GPU is used for rendering by default.
|
||||
|
||||
This is good and expected because the integrated GPU uses significantly less battery compared to the discrete GPU.
|
||||
However, this means that niri has to render the external monitor contents on the integrated GPU, then copy them over to the discrete GPU for display.
|
||||
On some laptops this can cause lag and stuttering (it gets worse with monitor resolution and refresh rate).
|
||||
|
||||
If your laptop has a MUX switch—usually a GPU toggle in the UEFI settings—then you can switch it to use the discrete GPU, then niri will render on the discrete GPU, and the external monitor won't lag.
|
||||
Otherwise, you can try configuring niri to render on the discrete GPU via the [`render-drm-device`](./Configuration:-Debug-Options.md#render-drm-device) debug option.
|
||||
|
||||
Keep in mind that using the discrete GPU for rendering will make the laptop's battery deplete much faster.
|
||||
|
||||
### How to run X11 apps like Steam or Discord?
|
||||
|
||||
To run X11 apps, you can use [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite).
|
||||
Check [the Xwayland wiki page](./Xwayland.md) for instructions.
|
||||
|
||||
Keep in mind that you can run many Electron apps such as VSCode natively on Wayland by passing the right flags, e.g. `code --ozone-platform-hint=auto`
|
||||
Keep in mind that you can run many Electron apps such as VSCode or Discord natively on Wayland by passing the right flags, as described [here](./Application-Issues.md#electron-applications).
|
||||
|
||||
### Why doesn't niri integrate Xwayland like other compositors?
|
||||
|
||||
@@ -66,14 +80,12 @@ I wouldn't be too surprised if, down the road, xwayland-satellite becomes the st
|
||||
|
||||
### Can I enable blur behind semitransparent windows?
|
||||
|
||||
Not yet, follow/upvote [this issue](https://github.com/YaLTeR/niri/issues/54).
|
||||
|
||||
There's also [a PR](https://github.com/YaLTeR/niri/pull/1634) adding blur to niri which you can build and run manually.
|
||||
Keep in mind that it's an experimental implementation that may have problems and performance concerns.
|
||||
<sup>Since: 26.04</sup> Yes.
|
||||
See the [window effects](./Window-Effects.md) wiki page.
|
||||
|
||||
### Can I make a window sticky / pinned / always on top / appear on all workspaces?
|
||||
|
||||
Not yet, follow/upvote [this issue](https://github.com/YaLTeR/niri/issues/932).
|
||||
Not yet, follow/upvote [this issue](https://github.com/niri-wm/niri/issues/932).
|
||||
|
||||
You can emulate this with a script that uses the niri IPC.
|
||||
For example, [nirius](https://git.sr.ht/~tsdh/nirius) seems to have this feature (`toggle-follow-mode`).
|
||||
@@ -82,4 +94,68 @@ For example, [nirius](https://git.sr.ht/~tsdh/nirius) seems to have this feature
|
||||
|
||||
Firefox seems to first open the Bitwarden window with a generic Firefox title, and only later change the window title to Bitwarden, so you can't effectively target it with an `open-floating` window rule.
|
||||
|
||||
You'll need to use a script, for example [this one](https://github.com/YaLTeR/niri/discussions/1599) or other ones (search niri issues and discussions for Bitwarden).
|
||||
You'll need to use a script, for example [this one](https://github.com/niri-wm/niri/discussions/1599) or other ones (search niri issues and discussions for Bitwarden).
|
||||
|
||||
### Can I open a window directly in the current column / in the same column as another window?
|
||||
|
||||
No, but you can script the behavior you want with the [niri IPC](./IPC.md).
|
||||
Listen to the event stream for a new window opening, then call an action like `consume-or-expel-window-left`.
|
||||
|
||||
Adding this directly to niri is challenging:
|
||||
|
||||
- The act of "opening a window directly in some column" by itself is quite involved. Niri will have to compute the exact initial window size provided how other windows in a column would resize in response. This logic exists, but it isn't directly pluggable to the code computing a size for a new window. Then, it'll need to handle all sorts of edge cases like the column disappearing, or new windows getting added to the column, before the target window had a chance to appear.
|
||||
- How do you indicate if a new window should spawn in an existing column (and in which one), as opposed to a new column? Different people seem to have different needs here (including very complex rules based on parent PID, etc.), and it's very unclear design-wise what kind of (simple) setting is actually needed and would be useful. See also https://github.com/niri-wm/niri/discussions/1125.
|
||||
|
||||
### Why does moving the mouse against a monitor edge focus the next window, but only sometimes?
|
||||
|
||||
This can happen with [`focus-follows-mouse`](./Configuration:-Input.md#focus-follows-mouse).
|
||||
When using client-side decorations, windows are supposed to have some margins outside their geometry for the mouse resizing handles.
|
||||
These margins "peek out" of the monitor edges since they're outside the window geometry, and `focus-follows-mouse` triggers when the mouse crosses them.
|
||||
|
||||
It doesn't always happen:
|
||||
|
||||
- Some toolkits don't put resize handles outside the window geometry. Then there's no input area outside, so nowhere for `focus-follows-mouse` to trigger.
|
||||
- If the current window has its own margin for resizing, and it extends all the way to the monitor edge, then `focus-follows-mouse` won't trigger because the mouse will never leave the current window.
|
||||
|
||||
To fix this, you can:
|
||||
|
||||
- Use `focus-follows-mouse max-scroll-amount="0%"`, which will prevent `focus-follows-mouse` from triggering when it would cause scrolling.
|
||||
- Set `prefer-no-csd` which will generally cause clients to remove those resizing margins.
|
||||
|
||||
### How do I recover from a dead screen locker / from a red screen?
|
||||
|
||||
When your screen locker dies, you will be left with a red screen.
|
||||
This is niri's locked session background.
|
||||
|
||||
You can recover from this by spawning a new screen locker.
|
||||
One way is to switch to a different TTY (with a shortcut like <kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>F3</kbd>) and spawning a screen locker to niri's Wayland display, e.g. `WAYLAND_DISPLAY=wayland-1 swaylock`.
|
||||
|
||||
Another way is to set `allow-when-locked=true` on your screen locker bind, then you can press it on the red screen to get a fresh screen locker.
|
||||
```kdl
|
||||
binds {
|
||||
Super+Alt+L allow-when-locked=true { spawn "swaylock"; }
|
||||
}
|
||||
```
|
||||
|
||||
### How do I change output configuration based on connected monitors?
|
||||
|
||||
If you require different output configurations depending on what outputs are connected then you can use [Kanshi](https://gitlab.freedesktop.org/emersion/kanshi).
|
||||
|
||||
Kanshi has its own simple configuration and communicates with niri via IPC. You may want to launch kanshi from the niri config.kdl e.g. `spawn-at-startup "/usr/bin/kanshi"`
|
||||
|
||||
For example, if you wish to scale your laptop display differently when an external monitor is connected, you might use a Kanshi config like this:
|
||||
```
|
||||
profile {
|
||||
output eDP-1 enable scale 1.0
|
||||
}
|
||||
|
||||
profile {
|
||||
output HDMI-A-1 enable scale 1.0 position 0,0
|
||||
output eDP-1 enable scale 1.25 position 1920,0
|
||||
}
|
||||
```
|
||||
|
||||
### Why does Firefox or Thunderbird have 1 px smaller border?
|
||||
|
||||
They draw their own 1 px dark border around the window, which obscures one pixel of niri's border.
|
||||
If you don't like this, set the [`clip-to-geometry true` window rule](./Configuration:-Window-Rules.md#clip-to-geometry).
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
There are several ways to make a window big on niri: maximizing the column, maximizing the window to edges, and fullscreening the window.
|
||||
Let's look at their differences.
|
||||
|
||||
## Maximized (full-width) columns
|
||||
|
||||
Maximizing the column via `maximize-column` (bound to <kbd>Mod</kbd><kbd>F</kbd> by default) expands its width to cover the whole screen.
|
||||
Maximized columns still leave space for [struts] and [gaps], and can contain multiple windows.
|
||||
The windows retain their borders.
|
||||
This is the simplest of the sizing modes, and is equivalent to `proportion 1.0` column width, or `set-column-width "100%"`.
|
||||
|
||||

|
||||
|
||||
You can make a window open in a maximized column with the [`open-maximized true`](./Configuration:-Window-Rules.md#open-maximized) window rule.
|
||||
|
||||
## Windows maximized to edges
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
You can maximize an individual window via `maximize-window-to-edges` (bound to <kbd>Mod</kbd><kbd>M</kbd> by default).
|
||||
This is the same maximize as you can find on other desktop environments and operating systems: it expands a window to the edges of the available screen area.
|
||||
You will still see your bar, but not struts, gaps, or borders.
|
||||
|
||||
Windows are aware of their maximized-to-edges status and generally respond by squaring their corners.
|
||||
Windows can also control maximizing-to-edges: when you click on the square icon in the window's titlebar, or double-click on the titlebar, the window will request niri to maximize or unmaximize itself.
|
||||
|
||||
You can put multiple maximized windows into a [tabbed column](./Tabs.md), but not into a regular column.
|
||||
|
||||

|
||||
|
||||
You can make a window open maximized-to-edges, or prevent a window from maximizing upon opening, with the [`open-maximized-to-edges`](./Configuration:-Window-Rules.md#open-maximized-to-edges) window rule.
|
||||
|
||||
## Fullscreen windows
|
||||
|
||||
Windows can go fullscreen, usually seen with video players, presentations or games.
|
||||
You can also force a window to go fullscreen via `fullscreen-window` (bound to <kbd>Mod</kbd><kbd>Shift</kbd><kbd>F</kbd> by default).
|
||||
Fullscreen windows cover the entire screen.
|
||||
Similarly to maximize-to-edges, windows are aware of their fullscreen status, and can respond by hiding their titlebars or other parts of the UI.
|
||||
|
||||
Niri renders a solid black backdrop behind fullscreen windows.
|
||||
This backdrop helps match the screen size when the window itself remains too small (e.g. if you try to fullscreen a fixed-size dialog window), which is the behavior [defined by the Wayland protocol](https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_fullscreen).
|
||||
|
||||
When a fullscreen window is focused and not animating, it will cover floating windows and the top layer-shell layer.
|
||||
If you want for example your layer-shell notifications or launcher to appear over fullscreen windows, configure the respective tools to put them on the overlay layer-shell layer.
|
||||
|
||||

|
||||
|
||||
You can make a window open fullscreen, or prevent a window from fullscreening upon opening, with the [`open-fullscreen`](./Configuration:-Window-Rules.md#open-fullscreen) window rule.
|
||||
|
||||
## Common behaviors across fullscreen and maximize
|
||||
|
||||
Fullscreen or maximized-to-edges windows can only be in the scrolling layout.
|
||||
So if you try to fullscreen or maximize a [floating window](./Floating-Windows.md), it'll move into the scrolling layout.
|
||||
Then, unfullscreening/unmaximizing will bring it back into the floating layout automatically.
|
||||
|
||||
Thanks to scrollable tiling, fullscreen and maximized windows remain a normal participant of the layout: you can scroll left and right from them and see other windows.
|
||||
|
||||

|
||||
|
||||
Fullscreen and maximize-to-edges are both special states that the windows are aware of and can control.
|
||||
Windows sometimes want to restore their fullscreen or, more frequently, maximized state when they open.
|
||||
The best opportunity for this is during the *initial configure* sequence when the window tells niri everything it should know before opening the window.
|
||||
If the window does this, then `open-maximized-to-edges` and `open-fullscreen` window rules have a chance to block or adjust the request.
|
||||
|
||||
However, some clients tend to request to be maximized shortly *after* the initial configure sequence, when the niri already sent them the initial size request (sometimes even after showing on screen, resulting in a quick resize right after opening).
|
||||
From niri's point of view, the window is already open by this point, so if the window does this, then the `open-maximized-to-edges` and `open-fullscreen` window rules don't do anything.
|
||||
|
||||
## Windowed fullscreen
|
||||
|
||||
<sup>Since: 25.05</sup>
|
||||
|
||||
Niri can also tell a window that it's in fullscreen without actually making it fullscreen, via the `toggle-windowed-fullscreen` action.
|
||||
This is generally useful for screencasting browser-based presentations, when you want to hide the browser UI, but still have the window sized as a normal window.
|
||||
|
||||
When in windowed fullscreen, you can use the niri action to maximize or unmaximize the window.
|
||||
Window-side titlebar maximize buttons and gestures may not work, since the window will always think that it's in fullscreen.
|
||||
|
||||
See also windowed fullscreen on the [screencasting features wiki page](./Screencasting.md#windowed-fakedetached-fullscreen).
|
||||
|
||||
|
||||
[struts]: ./Configuration:-Layout.md#struts
|
||||
[gaps]: ./Configuration:-Layout.md#gaps
|
||||
@@ -1,5 +1,39 @@
|
||||
## Quick start
|
||||
|
||||
Use these commands to install niri with [DankMaterialShell](https://github.com/AvengeMedia/DankMaterialShell) for a fairly out-of-the-box experience.
|
||||
|
||||
Fedora:
|
||||
```
|
||||
sudo dnf copr enable avengemedia/dms
|
||||
sudo dnf install niri dms
|
||||
systemctl --user add-wants niri.service dms
|
||||
```
|
||||
|
||||
Arch Linux:
|
||||
```
|
||||
sudo pacman -Syu niri xwayland-satellite xdg-desktop-portal-gnome xdg-desktop-portal-gtk alacritty dms-shell-niri matugen cava qt6-multimedia-ffmpeg
|
||||
systemctl --user add-wants niri.service dms
|
||||
```
|
||||
|
||||
Ubuntu 25.10 and above:
|
||||
```
|
||||
sudo add-apt-repository ppa:avengemedia/danklinux
|
||||
sudo add-apt-repository ppa:avengemedia/dms
|
||||
sudo apt install niri dms
|
||||
```
|
||||
|
||||
After running these commands, log out, choose Niri in your display manager, and log back in.
|
||||
Or, if not using a display manager, run `niri-session` on a TTY.
|
||||
|
||||
The default niri config will run Waybar, so you might get two bars on screen.
|
||||
To fix this, stop Waybar with `pkill waybar` command, then open `~/.config/niri/config.kdl` and delete the `spawn-at-startup "waybar"` line.
|
||||
|
||||
Check the DankMaterialShell's [compositor setup page](https://danklinux.com/docs/dankmaterialshell/compositors#niri-configuration) to learn how to configure DMS-specific binds and other niri integrations.
|
||||
|
||||
## Slower and more considered start
|
||||
|
||||
The easiest way to get niri is to install one of the distribution packages.
|
||||
Here are some of them: [Fedora COPR](https://copr.fedorainfracloud.org/coprs/yalter/niri/) and [nightly COPR](https://copr.fedorainfracloud.org/coprs/yalter/niri-git/) (which I maintain myself), [NixOS Flake](https://github.com/sodiboo/niri-flake), and some more from repology below.
|
||||
Here are some of them: [Fedora COPR](https://copr.fedorainfracloud.org/coprs/yalter/niri/) and [nightly COPR](https://copr.fedorainfracloud.org/coprs/yalter/niri-git/) (which I maintain myself), [NixOS Flake](https://github.com/sodiboo/niri-flake), and some more from repology below, including a [pacstall package](https://pacstall.dev/packages/niri/) for Debian-based distros.
|
||||
See the [Building](#building) section if you'd like to compile niri yourself and the [Packaging niri](./Packaging-niri.md) page if you want to package niri.
|
||||
|
||||
[](https://repology.org/project/niri/versions)
|
||||
@@ -76,7 +110,7 @@ If you still get a black screen, try using each of the `card` devices.
|
||||
There's a common problem of mesa drivers going out of sync with niri, so make sure your system mesa version matches the niri mesa version.
|
||||
When this happens, you usually see a black screen when trying to start niri from a TTY.
|
||||
|
||||
Also, on Intel graphics, you may need a workaround described [here](https://nixos.wiki/wiki/Intel_Graphics).
|
||||
Also, on Intel graphics, you may need a workaround described [here](https://wiki.nixos.org/wiki/Intel_Graphics).
|
||||
|
||||
### Virtual Machines
|
||||
|
||||
@@ -112,13 +146,10 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
|
||||
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>PageUp</kbd> | Move the focused column to the workspace above |
|
||||
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>PageDown</kbd> | Move the focused workspace down |
|
||||
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>PageUp</kbd> | Move the focused workspace up |
|
||||
| <kbd>Mod</kbd><kbd>,</kbd> | Consume the window to the right into the focused column |
|
||||
| <kbd>Mod</kbd><kbd>.</kbd> | Expel the bottom window in the focused column into its own column |
|
||||
| <kbd>Mod</kbd><kbd>[</kbd> | Consume or expel the focused window to the left |
|
||||
| <kbd>Mod</kbd><kbd>]</kbd> | Consume or expel the focused window to the right |
|
||||
| <kbd>Mod</kbd><kbd>R</kbd> | Toggle between preset column widths |
|
||||
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>R</kbd> | Toggle between preset column heights |
|
||||
| <kbd>Mod</kbd><kbd>F</kbd> | Maximize column |
|
||||
| <kbd>Mod</kbd><kbd>R</kbd> and <kbd>Mod</kbd><kbd>Shift</kbd><kbd>R</kbd> | Toggle between preset column widths forward and back |
|
||||
| <kbd>Mod</kbd><kbd>M</kbd> | Maximize window |
|
||||
| <kbd>Mod</kbd><kbd>C</kbd> | Center column within view |
|
||||
| <kbd>Mod</kbd><kbd>-</kbd> | Decrease column width by 10% |
|
||||
| <kbd>Mod</kbd><kbd>=</kbd> | Increase column width by 10% |
|
||||
@@ -190,7 +221,7 @@ This defaults to `/usr/bin/niri`.
|
||||
| `resources/niri.service` (systemd) | `/etc/systemd/user/` |
|
||||
| `resources/niri-shutdown.target` (systemd) | `/etc/systemd/user/` |
|
||||
| `resources/dinit/niri` (dinit) | `/etc/dinit.d/user/` |
|
||||
| `resources/dinit/niri-shutdown` (dinit) | `/etc/dinit.d/user/` |
|
||||
| `resources/dinit/niri.target` (dinit) | `/etc/dinit.d/user/` |
|
||||
|
||||
[Alacritty]: https://github.com/alacritty/alacritty
|
||||
[fuzzel]: https://codeberg.org/dnkl/fuzzel
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ To get a taste of the events, run `niri msg event-stream`.
|
||||
Though, this is more of a debug function than anything.
|
||||
You can get raw events from `niri msg --json event-stream`, or by connecting to the niri socket and requesting an event stream manually.
|
||||
|
||||
You can find the full list of events along with documentation [here](https://yalter.github.io/niri/niri_ipc/enum.Event.html).
|
||||
You can find the full list of events along with documentation [here](https://niri-wm.github.io/niri/niri_ipc/enum.Event.html).
|
||||
|
||||
### Programmatic Access
|
||||
|
||||
@@ -57,7 +57,7 @@ $ env NIRI_SOCKET=./temp.sock niri msg action focus-workspace 2
|
||||
{"Action":{"FocusWorkspace":{"reference":{"Index":2}}}}
|
||||
```
|
||||
|
||||
You can find all available requests and response types in the [niri-ipc sub-crate documentation](https://yalter.github.io/niri/niri_ipc/).
|
||||
You can find all available requests and response types in the [niri-ipc sub-crate documentation](https://niri-wm.github.io/niri/niri_ipc/).
|
||||
|
||||
### Backwards Compatibility
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ Note that if you're using the provided `resources/niri-portals.conf`, you also n
|
||||
|
||||
If you do not want to install `nautilus` (say you use `nemo` instead), you can set `org.freedesktop.impl.portal.FileChooser=gtk;` in `niri-portals.conf` to use the GTK portal for file chooser dialogues.
|
||||
|
||||
> [!WARNING]
|
||||
> Do not set the `GDK_BACKEND` environment variable globally as this will break the screencast portal.
|
||||
|
||||
### Authentication Agent
|
||||
|
||||
Required when apps need to ask for root permissions. Something like `plasma-polkit-agent` works fine. Start it [with systemd](./Example-systemd-Setup.md) or with [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup).
|
||||
|
||||
@@ -4,14 +4,18 @@ First, for creating a niri package, see the [Packaging](./Packaging-niri.md) pag
|
||||
### Configuration
|
||||
|
||||
Niri will load configuration from `$XDG_CONFIG_HOME/niri/config.kdl` or `~/.config/niri/config.kdl`, falling back to `/etc/niri/config.kdl`.
|
||||
If both of these files are missing, niri will create `$XDG_CONFIG_HOME/niri/config.kdl` with the contents of [the default configuration file](https://github.com/YaLTeR/niri/blob/main/resources/default-config.kdl), which are embedded into the niri binary at build time.
|
||||
If both of these files are missing, niri will create `$XDG_CONFIG_HOME/niri/config.kdl` with the contents of [the default configuration file](https://github.com/niri-wm/niri/blob/main/resources/default-config.kdl), which are embedded into the niri binary at build time.
|
||||
|
||||
This means that you can customize your distribution defaults by creating `/etc/niri/config.kdl`.
|
||||
When this file is present, niri *will not* automatically create a config at `~/.config/niri/`, so you'll need to direct your users how to do it themselves.
|
||||
|
||||
Keep in mind that we update the default config in new releases, so if you have a custom `/etc/niri/config.kdl`, you likely want to inspect and apply the relevant changes too.
|
||||
|
||||
Splitting the niri config file into multiple files, or includes, are not supported yet.
|
||||
The default configuration locations can be overridden with the `NIRI_CONFIG` environment variable.
|
||||
|
||||
<sup>Since: 26.04</sup> You can also change the configuration path at runtime via the niri IPC or using the command `niri msg action load-config-file --path <path-to-config.kdl>`.
|
||||
|
||||
<sup>Since: 25.11</sup> You can split the niri config file into multiple files using [`include`](./Configuration:-Include.md).
|
||||
|
||||
### Xwayland
|
||||
|
||||
@@ -32,7 +36,7 @@ Make sure your system installer sets the keyboard layout via systemd-localed, an
|
||||
### Autostart
|
||||
|
||||
Niri works with the normal systemd autostart.
|
||||
The default [niri.service](https://github.com/YaLTeR/niri/blob/main/resources/niri.service) brings up `graphical-session.target` as well as `xdg-desktop-autostart.target`.
|
||||
The default [niri.service](https://github.com/niri-wm/niri/blob/main/resources/niri.service) brings up `graphical-session.target` as well as `xdg-desktop-autostart.target`.
|
||||
|
||||
To make a program run at niri startup without editing the niri config, you can either link its .desktop to `~/.config/autostart/`, or use a .service file with `WantedBy=graphical-session.target`.
|
||||
See the [example systemd setup](./Example-systemd-Setup.md) page for some examples.
|
||||
@@ -51,11 +55,11 @@ This is detailed on the [Important Software](./Important-Software.md) page.
|
||||
|
||||
On top of that, you may want to preconfigure some desktop shell components to make the experience less barebones.
|
||||
Niri's default config spawns [Waybar](https://github.com/Alexays/Waybar), which is a good starting point, but you may want to consider changing its default configuration to be less of a kitchen sink, and adding the `niri/workspaces` module.
|
||||
You will probably also want a desktop background tool ([swaybg](https://github.com/swaywm/swaybg) or [swww](https://github.com/LGFae/swww)), and a nicer screen locker (compared to the default `swaylock`), like [hyprlock](https://github.com/hyprwm/hyprlock/).
|
||||
You will probably also want a desktop background tool ([swaybg](https://github.com/swaywm/swaybg) or [awww (which used to be swww)](https://codeberg.org/LGFae/awww/)), and a nicer screen locker (compared to the default `swaylock`), like [hyprlock](https://github.com/hyprwm/hyprlock/).
|
||||
|
||||
Alternatively, some desktop environments and shells work with niri, and can give a more cohesive experience in one package:
|
||||
|
||||
- [LXQt](https://lxqt-project.org/) officially supports niri, see [their wiki](https://github.com/lxqt/lxqt/wiki/ConfigWaylandSettings#general) for details on setting it up.
|
||||
- [LXQt](https://lxqt-project.org/) officially supports niri, see [their wiki](https://lxqt-project.org/wiki/Wayland-Session) for details on setting it up.
|
||||
- Many [XFCE](https://www.xfce.org/) components work on Wayland, including niri. See [their wiki](https://wiki.xfce.org/releng/wayland_roadmap#component_specific_status) for details.
|
||||
- There are complete desktop shells based on Quickshell that support niri, for example [DankMaterialShell](https://github.com/AvengeMedia/DankMaterialShell) and [Noctalia](https://github.com/noctalia-dev/noctalia-shell).
|
||||
- You can run a [COSMIC](https://system76.com/cosmic/) session with niri using [cosmic-ext-extra-sessions](https://github.com/Drakulix/cosmic-ext-extra-sessions).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Things to keep in mind with layer-shell components (bars, launchers, etc.):
|
||||
|
||||
1. When a full-screen window is active and covers the entire screen, it will render above the top layer, and it will be prioritized for keyboard focus. If your launcher uses the top layer, and you try to run it while looking at a full-screen window, it won't show up. Only the overlay layer will show up on top of full-screen windows.
|
||||
1. When a [full-screen](./Fullscreen-and-Maximize.md) window is active and covers the entire screen, it will render above the top layer, and it will be prioritized for keyboard focus. If your launcher uses the top layer, and you try to run it while looking at a full-screen window, it won't show up. Only the overlay layer will show up on top of full-screen windows.
|
||||
1. Components on the bottom and background layers will receive *on-demand* keyboard focus as expected. However, they will only receive *exclusive* keyboard focus when there are no windows on the workspace.
|
||||
1. When opening the [Overview](./Overview.md), components on the bottom and background layers will zoom out and remain on the workspaces, while the top and overlay layers remain on top of the Overview. So, if you want the bar to remain on top, put it on the *top* layer.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
The name "niri" is canonically written in lower-case, but feel free to capitalize it if you'd like, especially at the start of sentences where the grammatical rules require it.
|
||||
This name is not intended to mean or stand for anything.
|
||||
|
||||
Our logo comes in four versions: full-sized, simple full-sized, icon, and simple icon.
|
||||
The simple versions are single-color and suitable for smaller sizes.
|
||||
|
||||
| | full-sized | icon |
|
||||
|--------|:------------------------------:|:------------------------------:|
|
||||
| normal |  |  |
|
||||
| simple |  |  |
|
||||
|
||||
The logo is intentionally recolorable.
|
||||
In fact, there's [a webpage](https://nirilogo.raurutuchr.ink) that lets you quickly adjust the color and download an SVG.
|
||||
|
||||
All versions of the logo are licensed under [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
The full-sized logo is based on the [Cherry Bomb One](https://github.com/satsuyako/CherryBomb) font, licensed under the [SIL Open Font License 1.1](https://openfontlicense.org/).
|
||||
@@ -88,7 +88,7 @@ layer-rule {
|
||||
|
||||
This will only work for *background* layer surfaces that ignore exclusive zones (typical for wallpaper tools).
|
||||
|
||||
You can run two different wallpaper tools (like swaybg and swww), one for the backdrop and one for the normal workspace background.
|
||||
You can run two different wallpaper tools (like swaybg and awww), one for the backdrop and one for the normal workspace background.
|
||||
This way you could set the backdrop one to a blurred version of the wallpaper for a nice effect.
|
||||
|
||||
You can also combine this with a transparent background color if you don't like the wallpaper moving together with workspaces:
|
||||
|
||||
@@ -24,12 +24,31 @@ To do that, put files into the correct directories according to this table.
|
||||
| `resources/niri.service` (systemd) | `/usr/lib/systemd/user/` |
|
||||
| `resources/niri-shutdown.target` (systemd) | `/usr/lib/systemd/user/` |
|
||||
| `resources/dinit/niri` (dinit) | `/usr/lib/dinit.d/user/` |
|
||||
| `resources/dinit/niri-shutdown` (dinit) | `/usr/lib/dinit.d/user/` |
|
||||
| `resources/dinit/niri.target` (dinit) | `/usr/lib/dinit.d/user/` |
|
||||
|
||||
Doing this will make niri appear in GDM and other display managers.
|
||||
|
||||
See the [Integrating niri](./Integrating-niri.md) page for further information on distribution integration.
|
||||
|
||||
### Recommended dependencies
|
||||
|
||||
First of all, make sure niri depends on `libwayland-server`.
|
||||
This library is currently loaded dynamically, so it's not picked up as a dependency at niri build time.
|
||||
|
||||
Then, the following dependencies are optional, but strongly recommended.
|
||||
Set them as automatically-installed optional dependencies, if possible.
|
||||
|
||||
- `xwayland-satellite`: required to run X11 applications (Steam, Discord, etc.).
|
||||
- `xdg-desktop-portal-gnome`: required for screencasting.
|
||||
- `xdg-desktop-portal-gtk`: configured as the fallback portal in `niri-portals.conf`.
|
||||
(This is in general the standard fallback portal that you want installed.)
|
||||
- `gnome-keyring`: configured as the Secret portal provider in `niri-portals.conf`.
|
||||
- Your distro's GPU driver package, such as `mesa-dri-drivers` and `mesa-libEGL`.
|
||||
Working hardware acceleration is required for running niri.
|
||||
- Some notification daemon like `mako`, generally required for apps to work correctly.
|
||||
|
||||
Finally, you may want to auto-install some of the applications bound in niri's [default configuration file](https://github.com/niri-wm/niri/blob/main/resources/default-config.kdl) (search for `spawn`), such as `alacritty` and `fuzzel`.
|
||||
|
||||
### Running tests
|
||||
|
||||
A bulk of our tests spawn niri compositor instances and test Wayland clients.
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ Feel free to look through usage and [Getting started](./Getting-Started.md).
|
||||
If you're looking for ways to configure niri, check out the [introduction to configuration](./Configuration:-Introduction.md).
|
||||
|
||||
If you'd like to help with niri, there are plenty of both coding- and non-coding-related ways to do so.
|
||||
See [CONTRIBUTING.md](https://github.com/YaLTeR/niri/blob/main/CONTRIBUTING.md) for an overview.
|
||||
See [CONTRIBUTING.md](https://github.com/niri-wm/niri/blob/main/CONTRIBUTING.md) for an overview.
|
||||
|
||||
If you're not already here, check out our new wiki website! https://yalter.github.io/niri/
|
||||
If you're not already here, check out our new wiki website! https://niri-wm.github.io/niri/
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -137,4 +137,17 @@ Here's an example showing a windowed-fullscreen Google Slides [presentation](htt
|
||||
|
||||

|
||||
|
||||
### Screen mirroring
|
||||
|
||||
For presentations it can be useful to mirror an output to another.
|
||||
Currently, niri doesn't have built-in output mirroring, but you can use a third-party tool [`wl-mirror`](https://github.com/Ferdi265/wl-mirror) that mirrors an output to a window.
|
||||
Note that the command below requires [`jq`](https://jqlang.org/download/) to be installed.
|
||||
```kdl
|
||||
binds {
|
||||
Mod+P repeat=false { spawn-sh "wl-mirror $(niri msg --json focused-output | jq -r .name)"; }
|
||||
}
|
||||
```
|
||||
Focus the output you want to mirror, press <kbd>Mod</kbd><kbd>P</kbd> and move the `wl-mirror` window to the target output.
|
||||
Finally, fullscreen the `wl-mirror` window (by default, <kbd>Mod</kbd><kbd>Shift</kbd><kbd>F</kbd>).
|
||||
|
||||
[OBS]: https://obsproject.com/
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
### Overview
|
||||
|
||||
<sup>Since: 26.04</sup>
|
||||
|
||||
You can apply background effects to windows and layer-shell surfaces.
|
||||
These include blur, xray, saturation, and noise.
|
||||
They can be enabled in the `background-effect {}` section of [window](./Configuration:-Window-Rules.md#background-effect) or [layer](./Configuration:-Layer-Rules.md#background-effect) rules.
|
||||
|
||||
The window needs to be semitransparent for you to see the background effect (otherwise it's fully covered by the opaque window).
|
||||
Focus ring and border can also cover the background effect, see [this FAQ entry](./FAQ.md#why-are-transparent-windows-tinted-why-is-the-borderfocus-ring-showing-up-through-semitransparent-windows) for how to change this.
|
||||
|
||||
### Blur
|
||||
|
||||
Windows and layer surfaces can request their background to be blurred via the [`ext-background-effect` protocol](https://wayland.app/protocols/ext-background-effect-v1).
|
||||
In this case, the application will usually offer some "background blur" setting that you'll need to enable in its configuration.
|
||||
|
||||
You can also enable blur on the niri side with the `blur true` background effect window rule:
|
||||
|
||||
```kdl
|
||||
// Enable blur behind the Alacritty terminal.
|
||||
window-rule {
|
||||
match app-id="^Alacritty$"
|
||||
|
||||
background-effect {
|
||||
blur true
|
||||
}
|
||||
}
|
||||
|
||||
// Enable blur behind the fuzzel launcher.
|
||||
layer-rule {
|
||||
match namespace="^launcher$"
|
||||
|
||||
background-effect {
|
||||
blur true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Blur enabled via the window rule will follow the window corner radius set via [`geometry-corner-radius`](./Configuration:-Window-Rules.md#geometry-corner-radius).
|
||||
On the other hand, blur enabled through `ext-background-effect` will exactly follow the shape requested by the window.
|
||||
If the window or layer has clientside rounded corners or other complex shape, it should set a corresponding blur shape through `ext-background-effect`, then it will get correctly shaped background blur without any manual niri configuration.
|
||||
|
||||
Windows can also blur their pop-up menus using `ext-background-effect`.
|
||||
On the niri side, you can do it with a `popups` block inside [`window-rule`](./Configuration:-Window-Rules.md#popups) and [`layer-rule`](./Configuration:-Layer-Rules.md#popups).
|
||||
See those wiki pages for examples and limitations.
|
||||
|
||||
Global blur settings are configured in the [`blur {}` config section](./Configuration:-Miscellaneous.md#blur) and apply to all background blur.
|
||||
|
||||
### Xray
|
||||
|
||||
Xray makes the window background "see through" to your wallpaper, ignoring any other windows below.
|
||||
You can enable it with `xray true` background effect [window](./Configuration:-Window-Rules.md#background-effect) or [layer](./Configuration:-Layer-Rules.md#background-effect) rule.
|
||||
|
||||
Xray is automatically enabled by default if any other background effect (like blur) is active.
|
||||
This is because it's much more efficient: with xray active, niri only needs to blur the background once, and then can reuse this blurred version with no extra work (since the wallpaper changes very rarely).
|
||||
|
||||
If you have an animated wallpaper, xray will still have to recompute blur every frame, but that happens once and shared among all windows, rather than recomputed separately for each window.
|
||||
|
||||
#### Non-xray effects (experimental)
|
||||
|
||||
You can disable xray with `xray false` background effect window rule.
|
||||
This gives you the normal kind of blur where everything below a window is blurred.
|
||||
Keep in mind that non-xray blur and other non-xray effects are more expensive as niri has to recompute them any time you move the window, or the contents underneath change.
|
||||
|
||||
> [!WARNING]
|
||||
> Non-xray effects are currently experimental because they have some known limitations.
|
||||
>
|
||||
> - They disappear during window open/close animations and while dragging a tiled window.
|
||||
> Fixing this requires a refactor to the niri rendering code to defer offscreen rendering, and possibly other refactors.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
The `ext-background-effect` protocol supports any wl_surface.
|
||||
We currently implement it only for toplevels, layer surfaces, and pop-ups, which should cover the vast majority of what's actually used by applications.
|
||||
|
||||
For pop-ups, effects default to *non-xray* because pop-ups generally appear on top of windows.
|
||||
|
||||
In particular, the following surface types don't support `ext-background-effect`.
|
||||
They can be implemented as the need arises.
|
||||
|
||||
- Subsurfaces. Would require implementing `clip-to-geometry` support for background effects.
|
||||
- Lock surfaces. Not useful as it would just show our red locked session background.
|
||||
- Cursor and drag-and-drop icon.
|
||||
The main challenge here will be screencasts where the cursor is rendered separately.
|
||||
This is problematic because non-xray effects require rendering the whole scene in one go rather than separately.
|
||||
@@ -47,7 +47,7 @@ It will open as a new window.
|
||||
|
||||
This method involves invoking XWayland directly and running it as its own window, it also requires an extra X11 window manager running inside it.
|
||||
|
||||

|
||||

|
||||
|
||||
Here's how you do it:
|
||||
|
||||
@@ -121,6 +121,14 @@ Exec=cage -- flatpak run com.spotify.Client
|
||||
Terminal=false
|
||||
```
|
||||
|
||||
## Proton-GE native Wayland
|
||||
|
||||
It's possible to run some games as native Wayland clients, sidestepping the issues related to X11. You can do it with a custom version of Proton like [Proton-GE](https://github.com/GloriousEggroll/proton-ge-custom) by setting the `PROTON_ENABLE_WAYLAND=1` environmental variable in the game's launch parameters. Do note that for now this is an experimental feature, might not work with every game and might have its own issues.
|
||||
|
||||
```
|
||||
PROTON_ENABLE_WAYLAND=1 %command%
|
||||
```
|
||||
|
||||
## Using gamescope
|
||||
|
||||
You can use [gamescope](https://github.com/ValveSoftware/gamescope) to run X11 games and even Steam itself.
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
* [Nvidia](./Nvidia.md)
|
||||
* [Xwayland](./Xwayland.md)
|
||||
* [Gestures](./Gestures.md)
|
||||
* [Fullscreen and Maximize](./Fullscreen-and-Maximize.md)
|
||||
* [Window Effects](./Window-Effects.md)
|
||||
* [Packaging niri](./Packaging-niri.md)
|
||||
* [Integrating niri](./Integrating-niri.md)
|
||||
* [Accessibility](./Accessibility.md)
|
||||
* [Name and Logo](./Name-and-Logo.md)
|
||||
* [FAQ](./FAQ.md)
|
||||
|
||||
## Configuration
|
||||
@@ -31,7 +34,9 @@
|
||||
* [Layer Rules](./Configuration:-Layer-Rules.md)
|
||||
* [Animations](./Configuration:-Animations.md)
|
||||
* [Gestures](./Configuration:-Gestures.md)
|
||||
* [Recent Windows](./Configuration:-Recent-Windows.md)
|
||||
* [Debug Options](./Configuration:-Debug-Options.md)
|
||||
* [Include](./Configuration:-Include.md)
|
||||
|
||||
## Development
|
||||
* [Design Principles](./Development:-Design-Principles.md)
|
||||
|
||||
@@ -57,4 +57,10 @@
|
||||
|
||||
.md-typeset table:not([class]) td {
|
||||
padding: .5em 1.25em;
|
||||
}
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Improve keyboard shortcuts for screen readers: this way they won't break on them. */
|
||||
.md-typeset kbd {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bce4f9debe831beb28bc20ed96f699a4d67a587ecfaf47aff7266b04876b5d3a
|
||||
size 31393
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b854a03568b2b9505e760cba957d82facfc94138b1f8a2b0c173f80cc6633734
|
||||
size 8495
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fc31d15494c4fefedcad664d79838ae33e6bc3ef9f06b4413f92df4b0dcf374c
|
||||
size 47121
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b5a63ea3cc2f158e175c00dd058988a2bbf676e2a2aac5c2ef1603bd983589d5
|
||||
size 166777
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bef0c57d617916bf6014fe08e268c8201d7f6ef682e3aea3395e76116b1d0400
|
||||
size 56936
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1f4cc85f606afb37b2b4490f5bdc64ddaf509210bdf59007eb853bb15d5959c4
|
||||
size 14069
|
||||
@@ -0,0 +1,110 @@
|
||||
<svg width="300" height="300" viewBox="0 0 83.343752 83.343748" version="1.1" id="svg1" sodipodi:docname="smol-icon.svg" inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview id="namedview1" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="1" inkscape:zoom="0.8030909" inkscape:cx="63.504642" inkscape:cy="136.9708" inkscape:window-width="2528" inkscape:window-height="1408" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="0" inkscape:current-layer="layer1"/>
|
||||
<defs id="defs1">
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter21" x="-0.35129356" y="-0.93287116" width="1.7025871" height="2.8657422">
|
||||
<feGaussianBlur stdDeviation="5.0792937" id="feGaussianBlur21"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter22" x="-0.42891073" y="-0.64938289" width="1.8578213" height="2.2987657">
|
||||
<feGaussianBlur stdDeviation="6.5904956" id="feGaussianBlur22"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter23" x="-0.41511482" y="-0.64325017" width="1.8302296" height="2.2865005">
|
||||
<feGaussianBlur stdDeviation="7.8584288" id="feGaussianBlur23"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter24" x="-0.36140347" y="-0.92623633" width="1.7228069" height="2.8524725">
|
||||
<feGaussianBlur stdDeviation="5.269127" id="feGaussianBlur24"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter27" x="-0.17576355" y="-0.83601773" width="1.3515271" height="2.6720355">
|
||||
<feGaussianBlur stdDeviation="4.753432" id="feGaussianBlur26"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter52" x="-0.33025387" y="-1.0968854" width="1.6605078" height="3.1937709">
|
||||
<feGaussianBlur stdDeviation="4.2575558" id="feGaussianBlur52"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter53" x="-0.34014133" y="-1.0243618" width="1.6802826" height="3.0487237">
|
||||
<feGaussianBlur stdDeviation="4.3187927" id="feGaussianBlur54"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter55" x="-0.33171228" y="-1.092941" width="1.6634245" height="3.1858823">
|
||||
<feGaussianBlur stdDeviation="4.4263355" id="feGaussianBlur55"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter56" x="-0.3459667" y="-0.98818076" width="1.6919334" height="2.9763615">
|
||||
<feGaussianBlur stdDeviation="4.9267071" id="feGaussianBlur56"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter57" x="-0.50604737" y="-0.53225249" width="2.0120947" height="2.0645051">
|
||||
<feGaussianBlur stdDeviation="5.8261111" id="feGaussianBlur57"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter58" x="-0.32130077" y="-1.1307663" width="1.6426016" height="3.2615325">
|
||||
<feGaussianBlur stdDeviation="4.2390405" id="feGaussianBlur58"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter59" x="-0.4030115" y="-0.71156067" width="1.806023" height="2.4231212">
|
||||
<feGaussianBlur stdDeviation="4.4950086" id="feGaussianBlur59"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter60" x="-0.1759294" y="-1.0177407" width="1.3518589" height="3.0354815">
|
||||
<feGaussianBlur stdDeviation="4.8003951" id="feGaussianBlur60"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter70" x="-0.44225678" y="-0.3197549" width="1.8841637" height="1.6395177">
|
||||
<feGaussianBlur stdDeviation="18.461288" id="feGaussianBlur70"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter72" x="-0.43271318" y="-0.31310251" width="1.8654264" height="1.626205">
|
||||
<feGaussianBlur stdDeviation="18.509006" id="feGaussianBlur72"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter1" x="-0.45705071" y="-0.57220238" width="1.9141014" height="2.1444046">
|
||||
<feGaussianBlur stdDeviation="8.5810337" id="feGaussianBlur1"/>
|
||||
</filter>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter2" x="-0.545201" y="-0.88686216" width="2.0904019" height="2.7737243">
|
||||
<feGaussianBlur stdDeviation="4.3199889" id="feGaussianBlur2"/>
|
||||
</filter>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z" id="path5"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath5">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z" id="path8"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath8">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z" id="path11"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath11">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z" id="path12"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath12">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z" id="path14"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath15">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 511.63431,224.36663 q -8.98433,0 -13.47647,-5.16598 -4.26755,-5.16599 -5.83981,-15.72256 -1.34765,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34763,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49214,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49217,4.94137 6.06443,15.72256 1.57223,10.78118 1.57223,27.62678 0.22461,16.8456 -1.34762,27.62679 -1.57226,10.55657 -6.06444,15.72256 -4.49215,5.16598 -13.47647,5.16598 z" id="path16"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath17">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 681.32926,224.3022 q -8.98433,0 -13.47647,-5.16597 -4.26755,-5.16599 -5.83981,-15.72257 -1.34765,-10.78118 -1.57226,-27.62678 0,-16.8456 1.34763,-27.62678 1.57226,-10.78119 6.06444,-15.72257 4.49214,-5.16598 13.47647,-5.16598 8.98432,0 13.25187,5.16598 4.49217,4.94138 6.06443,15.72257 1.57223,10.78118 1.57223,27.62678 0.22461,16.8456 -1.34762,27.62678 -1.57226,10.55658 -6.06444,15.72257 -4.49215,5.16597 -13.47647,5.16597 z" id="path17"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath18">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z" id="path19"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath19">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z" id="path20"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath20">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z" id="path21"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath21">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z" id="path22"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath23">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 681.43807,224.36663 q -8.98432,0 -13.47647,-5.16598 -4.26757,-5.16599 -5.83983,-15.72256 -1.34763,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34765,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49215,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49215,4.94137 6.06441,15.72256 1.57226,10.78118 1.57226,27.62678 0.2246,16.8456 -1.34766,27.62679 -1.57223,10.55657 -6.06441,15.72256 -4.49215,5.16598 -13.47647,5.16598 z" id="path25"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath25">
|
||||
<path style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 681.43807,224.36663 q -8.98432,0 -13.47647,-5.16598 -4.26757,-5.16599 -5.83983,-15.72256 -1.34763,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34765,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49215,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49215,4.94137 6.06441,15.72256 1.57226,10.78118 1.57226,27.62678 0.2246,16.8456 -1.34766,27.62679 -1.57223,10.55657 -6.06441,15.72256 -4.49215,5.16598 -13.47647,5.16598 z" id="path26"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath34">
|
||||
<path id="path35" style="fill:#d55c44;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 169.33334,220.13334 c 0,8.46667 -4.23334,16.93333 -33.86667,16.93333 -29.63333,0 -33.86667,-8.46666 -33.86667,-16.93333 0,-12.7 8.46667,-21.16667 33.86667,-21.16667 25.4,0 33.86667,8.46667 33.86667,21.16667 z"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath35">
|
||||
<path id="path36" style="fill:#d55c44;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers" d="m 715.91343,141.3418 c 0,8.46667 -4.23334,16.93332 -33.86667,16.93332 -29.63333,0 -33.86667,-8.46665 -33.86667,-16.93332 0,-12.7 8.46667,-21.16668 33.86667,-21.16668 25.4,0 33.86667,8.46668 33.86667,21.16668 z"/>
|
||||
</clipPath>
|
||||
<filter style="color-interpolation-filters:sRGB" id="filter61" x="-0.43233621" y="-0.31282973" width="1.8646724" height="1.6256595">
|
||||
<feGaussianBlur stdDeviation="18.492881" id="feGaussianBlur61"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="layer1" transform="translate(255.60477,-19.034522)">
|
||||
<g id="g71" transform="matrix(0.35676509,0,0,0.35676509,-261.36445,12.212049)" inkscape:label="ICON">
|
||||
<path id="path2" style="fill:#d55c44;fill-opacity:1;stroke-width:2.03467;stroke-linecap:square;paint-order:stroke fill markers" d="m 166.01229,213.24524 c 0,8.13867 -4.06933,16.27733 -32.55465,16.27733 -28.48531,0 -32.55465,-8.13866 -32.55465,-16.27733 0,-12.20799 8.13867,-20.34665 32.55465,-20.34665 24.41599,0 32.55465,8.13866 32.55465,20.34665 z"/>
|
||||
<path style="display:inline;mix-blend-mode:normal;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.03467;stroke-linecap:square;stroke-opacity:1;paint-order:stroke fill markers" d="m 100.90299,42.333334 c 16.27733,0 81.38662,48.831978 81.38662,89.525286 0,27.46519 -12.873,39.48278 -24.13834,44.74117 -8.53413,3.98352 -10.89852,-1.32846 -6.15196,-9.50569 3.18914,-5.49417 5.87432,-12.14352 5.87432,-18.95816 0,-12.20799 -4.06933,-20.34665 -12.20799,-28.48531 -8.13867,-8.13867 -18.59284,-12.20801 -24.41599,-12.208 -8.13866,1e-5 -12.20799,23.92152 -12.20799,36.62398 0,10.1218 3.02116,19.23655 6.05762,25.84132 2.86371,6.22899 0.46637,10.50739 -5.956,8.14525 -7.01677,-2.58076 -15.430493,-7.67367 -20.448281,-17.70925 -8.138661,-16.27732 -4.069331,-39.70212 -4.069331,-61.039962 0,-32.554647 -6e-6,-56.970634 16.277322,-56.970634 z" id="path3" inkscape:label="FLAME"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,418 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="300"
|
||||
height="300"
|
||||
viewBox="0 0 135.46667 135.46668"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="processed-icon.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:zoom="1.5347643"
|
||||
inkscape:cx="193.84084"
|
||||
inkscape:cy="116.95607"
|
||||
inkscape:window-width="1653"
|
||||
inkscape:window-height="1048"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g71"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
inkscape:deskcolor="#505050" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="linearGradient1"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
style="stop-color:#d8485f;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1" />
|
||||
<stop
|
||||
style="stop-color:#d88748;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop2" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter21"
|
||||
x="-0.35129357"
|
||||
y="-0.93287114"
|
||||
width="1.7025871"
|
||||
height="2.8657423">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5.0792937"
|
||||
id="feGaussianBlur21" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter22"
|
||||
x="-0.42891072"
|
||||
y="-0.64938287"
|
||||
width="1.8578214"
|
||||
height="2.2987657">
|
||||
<feGaussianBlur
|
||||
stdDeviation="6.5904956"
|
||||
id="feGaussianBlur22" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter23"
|
||||
x="-0.41511482"
|
||||
y="-0.64325018"
|
||||
width="1.8302296"
|
||||
height="2.2865004">
|
||||
<feGaussianBlur
|
||||
stdDeviation="7.8584288"
|
||||
id="feGaussianBlur23" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter24"
|
||||
x="-0.36140346"
|
||||
y="-0.92623631"
|
||||
width="1.7228069"
|
||||
height="2.8524726">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5.269127"
|
||||
id="feGaussianBlur24" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter27"
|
||||
x="-0.3220365"
|
||||
y="-1.5317638"
|
||||
width="1.644073"
|
||||
height="4.0635276">
|
||||
<feGaussianBlur
|
||||
stdDeviation="8.7093071"
|
||||
id="feGaussianBlur26" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter52"
|
||||
x="-0.33025388"
|
||||
y="-1.0968854"
|
||||
width="1.6605078"
|
||||
height="3.1937708">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.2575558"
|
||||
id="feGaussianBlur52" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter53"
|
||||
x="-0.34014132"
|
||||
y="-1.0243618"
|
||||
width="1.6802826"
|
||||
height="3.0487236">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.3187927"
|
||||
id="feGaussianBlur54" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter55"
|
||||
x="-0.33171227"
|
||||
y="-1.0929411"
|
||||
width="1.6634245"
|
||||
height="3.1858823">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.4263355"
|
||||
id="feGaussianBlur55" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter56"
|
||||
x="-0.34596671"
|
||||
y="-0.98818076"
|
||||
width="1.6919334"
|
||||
height="2.9763615">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.9267071"
|
||||
id="feGaussianBlur56" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter57"
|
||||
x="-0.50604737"
|
||||
y="-0.53225252"
|
||||
width="2.0120947"
|
||||
height="2.064505">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5.8261111"
|
||||
id="feGaussianBlur57" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter58"
|
||||
x="-0.32130078"
|
||||
y="-1.1307663"
|
||||
width="1.6426016"
|
||||
height="3.2615325">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.2390405"
|
||||
id="feGaussianBlur58" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter59"
|
||||
x="-0.4030115"
|
||||
y="-0.71156066"
|
||||
width="1.806023"
|
||||
height="2.4231213">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.4950086"
|
||||
id="feGaussianBlur59" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter60"
|
||||
x="-0.53873945"
|
||||
y="-1.1033097"
|
||||
width="2.0774789"
|
||||
height="3.2066195">
|
||||
<feGaussianBlur
|
||||
stdDeviation="14.7 5.204"
|
||||
id="feGaussianBlur60" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter70"
|
||||
x="-0.43159762"
|
||||
y="-0.3122953"
|
||||
width="1.8631952"
|
||||
height="1.6245906">
|
||||
<feGaussianBlur
|
||||
stdDeviation="18.461288"
|
||||
id="feGaussianBlur70" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter72"
|
||||
x="-0.43271319"
|
||||
y="-0.31310251"
|
||||
width="1.8654264"
|
||||
height="1.626205">
|
||||
<feGaussianBlur
|
||||
stdDeviation="18.509006"
|
||||
id="feGaussianBlur72" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter1"
|
||||
x="-0.45705071"
|
||||
y="-0.57220236"
|
||||
width="1.9141014"
|
||||
height="2.1444047">
|
||||
<feGaussianBlur
|
||||
stdDeviation="8.5810337"
|
||||
id="feGaussianBlur1" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter2"
|
||||
x="-0.545201"
|
||||
y="-0.88686217"
|
||||
width="2.090402"
|
||||
height="2.7737243">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.3199889"
|
||||
id="feGaussianBlur2" />
|
||||
</filter>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath1">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path5" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath5">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path8" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath8">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path11" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath11">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path12" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath12">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path14" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath15">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 511.63431,224.36663 q -8.98433,0 -13.47647,-5.16598 -4.26755,-5.16599 -5.83981,-15.72256 -1.34765,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34763,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49214,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49217,4.94137 6.06443,15.72256 1.57223,10.78118 1.57223,27.62678 0.22461,16.8456 -1.34762,27.62679 -1.57226,10.55657 -6.06444,15.72256 -4.49215,5.16598 -13.47647,5.16598 z"
|
||||
id="path16" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath17">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 681.32926,224.3022 q -8.98433,0 -13.47647,-5.16597 -4.26755,-5.16599 -5.83981,-15.72257 -1.34765,-10.78118 -1.57226,-27.62678 0,-16.8456 1.34763,-27.62678 1.57226,-10.78119 6.06444,-15.72257 4.49214,-5.16598 13.47647,-5.16598 8.98432,0 13.25187,5.16598 4.49217,4.94138 6.06443,15.72257 1.57223,10.78118 1.57223,27.62678 0.22461,16.8456 -1.34762,27.62678 -1.57226,10.55658 -6.06444,15.72257 -4.49215,5.16597 -13.47647,5.16597 z"
|
||||
id="path17" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath18">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path19" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath19">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path20" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath20">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path21" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath21">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path22" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath23">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 681.43807,224.36663 q -8.98432,0 -13.47647,-5.16598 -4.26757,-5.16599 -5.83983,-15.72256 -1.34763,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34765,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49215,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49215,4.94137 6.06441,15.72256 1.57226,10.78118 1.57226,27.62678 0.2246,16.8456 -1.34766,27.62679 -1.57223,10.55657 -6.06441,15.72256 -4.49215,5.16598 -13.47647,5.16598 z"
|
||||
id="path25" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath25">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#bf4040;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 681.43807,224.36663 q -8.98432,0 -13.47647,-5.16598 -4.26757,-5.16599 -5.83983,-15.72256 -1.34763,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34765,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49215,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49215,4.94137 6.06441,15.72256 1.57226,10.78118 1.57226,27.62678 0.2246,16.8456 -1.34766,27.62679 -1.57223,10.55657 -6.06441,15.72256 -4.49215,5.16598 -13.47647,5.16598 z"
|
||||
id="path26" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath34">
|
||||
<path
|
||||
id="path35"
|
||||
style="fill:#bf4040;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 169.33334,220.13334 c 0,8.46667 -4.23334,16.93333 -33.86667,16.93333 -29.63333,0 -33.86667,-8.46666 -33.86667,-16.93333 0,-12.7 8.46667,-21.16667 33.86667,-21.16667 25.4,0 33.86667,8.46667 33.86667,21.16667 z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath35">
|
||||
<path
|
||||
id="path36"
|
||||
style="fill:#bf4040;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 715.91343,141.3418 c 0,8.46667 -4.23334,16.93332 -33.86667,16.93332 -29.63333,0 -33.86667,-8.46665 -33.86667,-16.93332 0,-12.7 8.46667,-21.16668 33.86667,-21.16668 25.4,0 33.86667,8.46668 33.86667,21.16668 z" />
|
||||
</clipPath>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter61"
|
||||
x="-0.43233622"
|
||||
y="-0.31282974"
|
||||
width="1.8646724"
|
||||
height="1.6256595">
|
||||
<feGaussianBlur
|
||||
stdDeviation="18.492881"
|
||||
id="feGaussianBlur61" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1"
|
||||
id="linearGradient2"
|
||||
x1="-3.6298752"
|
||||
y1="229.95894"
|
||||
x2="160.69257"
|
||||
y2="24.819994"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(255.60477,-19.034522)">
|
||||
<g
|
||||
id="g71"
|
||||
transform="matrix(0.53845513,0,0,0.53845513,-260.51955,13.25883)"
|
||||
inkscape:label="ICON">
|
||||
<g
|
||||
id="g4"
|
||||
inkscape:label="OUTLINE">
|
||||
<path
|
||||
id="path1"
|
||||
style="display:inline;fill:#2d2d2d;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 131.35535,193.41317 c -8.42529,0.21555 -17.07195,1.23898 -24.70899,5.03516 -6.56852,3.28887 -11.780948,9.56904 -13.037575,16.89416 -1.12503,5.50318 -0.847502,11.50687 1.819693,16.54193 2.619641,4.74577 7.282642,8.08459 12.343352,9.82958 7.20278,2.70364 14.97875,3.20323 22.59699,3.46523 7.89843,0.20478 15.85819,-0.17121 23.61105,-1.76836 5.63625,-1.25422 11.31076,-3.51115 15.35105,-7.77992 3.29144,-3.52234 5.05422,-8.32066 5.11255,-13.1185 0.31661,-6.70741 -1.69285,-13.72981 -6.39837,-18.65616 -4.18578,-4.5464 -10.04779,-7.11576 -15.95626,-8.569 -6.77112,-1.66654 -13.79057,-1.92415 -20.73349,-1.87412 z" />
|
||||
<path
|
||||
id="path4"
|
||||
style="display:inline;mix-blend-mode:normal;fill:#2d2d2d;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 98.902224,36.772549 c -6.995893,0.126233 -13.62619,4.553849 -16.638172,10.834978 -3.470673,6.368967 -4.476788,13.68672 -5.225527,20.799337 -1.546324,15.903589 -0.51861,31.905986 -1.354082,47.842876 -0.544064,12.91133 -1.490643,25.96714 0.724426,38.77934 1.594594,9.15149 5.382372,18.1514 11.945332,24.8552 5.649973,5.83947 12.892719,10.17523 20.761639,12.23377 4.29395,0.94959 9.28733,-0.57303 11.73776,-4.36606 2.38164,-3.59662 2.34796,-8.32076 0.82613,-12.24486 -1.20789,-3.11502 -2.64307,-6.14315 -3.64842,-9.33584 -1.12185,-3.39459 -1.94228,-6.99797 -2.3869,-10.47882 -0.55748,-4.05967 -0.49766,-8.22945 -0.15435,-12.24032 0.58298,-6.27022 1.46357,-12.5645 3.39036,-18.57849 0.79153,-2.26867 1.52928,-4.64049 3.02766,-6.55814 5.54574,0.77098 10.63142,3.47347 15.10865,6.73077 1.80507,1.34294 3.49258,2.79574 5.00233,4.33482 4.14952,4.21383 7.69916,9.25861 9.14438,15.0671 1.23316,4.77068 1.46924,9.85005 0.41871,14.67703 -1.02032,4.50982 -3.25363,8.60065 -5.45452,12.62181 -1.87577,3.646 -3.05511,7.8744 -2.17051,11.96837 0.96209,4.20638 4.82507,7.5927 9.1658,7.8093 4.52117,0.38592 8.79301,-1.6188 12.64582,-3.75368 9.91038,-5.51624 17.57193,-14.7796 21.44242,-25.41236 3.56981,-9.82442 4.74142,-20.45137 3.89537,-30.84701 -1.23706,-11.29301 -6.03879,-21.87272 -12.00583,-31.42104 -8.7119,-13.884894 -20.08721,-25.908588 -32.38582,-36.666079 -9.92644,-8.582365 -20.62052,-16.394651 -32.3549,-22.32888 -4.66406,-2.261816 -9.65523,-4.278849 -14.913576,-4.33312 -0.18139,0.0033 -0.36279,0.0067 -0.54418,0.01 z" />
|
||||
</g>
|
||||
<path
|
||||
id="path2"
|
||||
style="display:inline;fill:url(#linearGradient2);fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="M 101.5994 42.333286 C 84.666067 42.333286 84.666625 67.732989 84.666625 101.59965 C 84.666625 123.79745 80.433973 148.16671 88.900639 165.10004 C 94.120654 175.54007 102.8727 180.83855 110.17226 183.52332 C 116.85346 185.98066 119.34762 181.52878 116.3685 175.04874 C 113.20966 168.17779 110.06743 158.69699 110.06743 148.16726 C 110.06743 134.95286 114.29954 110.06605 122.7662 110.06604 C 128.82404 110.06603 139.70034 114.29978 148.16701 122.76645 C 156.63368 131.23312 160.86741 139.69963 160.86741 152.39963 C 160.86741 159.48891 158.07401 166.40619 154.75634 172.12178 C 149.81849 180.62857 152.27761 186.15558 161.15568 182.01152 C 172.87504 176.54121 186.26658 164.03894 186.26658 135.46685 C 186.26658 93.13352 118.53274 42.333286 101.5994 42.333286 z M 135.4666 198.96723 C 110.0666 198.96723 101.5994 207.43403 101.5994 220.13403 C 101.5994 228.6007 105.83327 237.06681 135.4666 237.06681 C 165.09993 237.06681 169.3338 228.6007 169.3338 220.13403 C 169.3338 207.43403 160.8666 198.96723 135.4666 198.96723 z " />
|
||||
<g
|
||||
id="g26"
|
||||
clip-path="none"
|
||||
style="display:inline"
|
||||
inkscape:label="STUB">
|
||||
<path
|
||||
style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:0.745813;fill-rule:nonzero;stroke:none;stroke-width:1.292;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;filter:url(#filter27)"
|
||||
d="m 649.45734,133.81724 c 0.30861,-0.52382 1.24567,-1.10943 2.25778,-1.9344 0.71834,-0.58551 1.60294,-1.1585 2.689,-1.71704 1.33566,-0.68692 2.82604,-1.27591 4.41873,-1.81477 0.91309,-0.30893 1.90318,-0.61043 2.97552,-0.89949 2.75909,-0.74374 5.93213,-1.36936 9.45117,-1.81993 3.4485,-0.44153 7.02191,-0.68799 10.54487,-0.74159 0.11888,-0.002 0.23758,-0.003 0.35611,-0.005 3.42087,-0.0395 6.87572,0.10105 10.20745,0.42751 3.52675,0.34557 6.72,0.8799 9.50577,1.55294 1.13268,0.27365 2.17777,0.56526 3.14025,0.86932 1.5536,0.49082 3.01719,1.0374 4.3387,1.68661 1.10269,0.54172 2.00605,1.10621 2.74422,1.69041 1.03948,0.82267 1.92442,1.4682 2.24848,1.99812 -0.19311,-0.57592 -0.56732,-1.6128 -1.44388,-2.71822 -0.62575,-0.78911 -1.46647,-2.06647 -2.53663,-2.85214 -1.27528,-0.93627 -2.69843,-2.19648 -4.28815,-2.8662 -0.98885,-0.41658 -2.24548,-1.23914 -3.41727,-1.59811 -2.87657,-0.88121 -5.61102,-1.58246 -9.24402,-2.0046 -3.43498,-0.39913 -7.78163,-0.93367 -11.29835,-0.89735 -0.12203,0.001 -0.24424,0.003 -0.36663,0.005 -3.62086,0.0519 -8.29298,0.61115 -11.8461,1.12441 -3.62258,0.5233 -6.54204,1.50557 -9.38537,2.45693 -1.10746,0.37055 -2.4353,1.22472 -3.37108,1.64065 -1.62494,0.72224 -2.75555,1.66679 -4.03455,2.65099 -1.04655,0.80534 -1.65707,2.24134 -2.25723,3.03279 -0.84139,1.10955 -1.24261,2.1654 -1.41747,2.7334 z"
|
||||
id="path24"
|
||||
transform="translate(-546.58009,78.791545)"
|
||||
clip-path="url(#clipPath35)"
|
||||
sodipodi:nodetypes="csssssccssssscsssssccssssscc" />
|
||||
<path
|
||||
style="mix-blend-mode:normal;fill:#000000;fill-opacity:0.747711;fill-rule:nonzero;stroke:none;stroke-width:1.292;stroke-linecap:round;stroke-linejoin:round;filter:url(#filter60)"
|
||||
d="m 102.62873,226.09888 c 0.21837,0.59119 0.61147,1.63901 1.62645,2.90953 0.71917,0.90024 1.67343,1.81283 2.88011,2.66136 0.63885,0.44924 1.326,0.86437 2.04583,1.23422 1.73607,0.89199 3.88794,1.6902 6.44451,2.35832 3.15978,0.82576 6.73316,1.3998 10.52375,1.74639 2.77174,0.25344 5.543,0.37487 8.21345,0.40382 1.03453,0.0112 2.07172,0.008 3.10774,-0.0105 3.80268,-0.0675 7.55355,-0.33754 11.05616,-0.82447 3.61058,-0.50195 6.89851,-1.22612 9.69476,-2.17075 0.18263,-0.0617 0.36305,-0.1243 0.54123,-0.1878 2.50252,-0.89183 4.82056,-2.11237 6.51802,-3.52561 0.94359,-0.78561 1.6151,-1.56242 2.06492,-2.2946 0.63371,-1.03151 0.74175,-1.832 0.76927,-2.29791 -0.27946,0.40619 -0.77594,0.89664 -1.67451,1.38662 -0.63117,0.34418 -1.42595,0.67135 -2.41953,0.98728 -1.84319,0.58607 -3.98976,1.02107 -6.42445,1.47239 -0.16947,0.0314 -0.34092,0.0627 -0.51435,0.094 -2.66528,0.48001 -5.74678,0.9266 -9.18723,1.27047 -3.33185,0.33301 -6.89061,0.55814 -10.52391,0.6262 -0.98784,0.0185 -1.97654,0.0253 -2.96263,0.0197 -2.54936,-0.0146 -5.17908,-0.11853 -7.80849,-0.31818 -3.6162,-0.27458 -6.92286,-0.70577 -9.89494,-1.23993 -2.40547,-0.43232 -4.36461,-0.8899 -6.04479,-1.31771 -0.70208,-0.17876 -1.35634,-0.34389 -2.02406,-0.52251 -1.24448,-0.33289 -2.31304,-0.65483 -3.26912,-1.01993 -1.3453,-0.51373 -2.22471,-1.029 -2.73819,-1.44035 z"
|
||||
id="path59"
|
||||
clip-path="url(#clipPath34)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,391 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="253.46544"
|
||||
height="134.92722"
|
||||
viewBox="0 0 123.86632 37.866682"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="niri-logo-smol.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
inkscape:export-filename="niri-logo-smol.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:zoom="0.2284248"
|
||||
inkscape:cx="76.611646"
|
||||
inkscape:cy="89.745071"
|
||||
inkscape:window-width="990"
|
||||
inkscape:window-height="1048"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter21"
|
||||
x="-0.35129356"
|
||||
y="-0.93287116"
|
||||
width="1.7025871"
|
||||
height="2.8657422">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5.0792937"
|
||||
id="feGaussianBlur21" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter22"
|
||||
x="-0.42891073"
|
||||
y="-0.64938289"
|
||||
width="1.8578213"
|
||||
height="2.2987657">
|
||||
<feGaussianBlur
|
||||
stdDeviation="6.5904956"
|
||||
id="feGaussianBlur22" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter23"
|
||||
x="-0.41511482"
|
||||
y="-0.64325017"
|
||||
width="1.8302296"
|
||||
height="2.2865005">
|
||||
<feGaussianBlur
|
||||
stdDeviation="7.8584288"
|
||||
id="feGaussianBlur23" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter24"
|
||||
x="-0.36140347"
|
||||
y="-0.92623633"
|
||||
width="1.7228069"
|
||||
height="2.8524725">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5.269127"
|
||||
id="feGaussianBlur24" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter27"
|
||||
x="-0.30853596"
|
||||
y="-1.6721658"
|
||||
width="1.6170719"
|
||||
height="4.3443317">
|
||||
<feGaussianBlur
|
||||
stdDeviation="8.2543341"
|
||||
id="feGaussianBlur26" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter52"
|
||||
x="-0.33025387"
|
||||
y="-1.0968854"
|
||||
width="1.6605078"
|
||||
height="3.1937709">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.2575558"
|
||||
id="feGaussianBlur52" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter53"
|
||||
x="-0.34014133"
|
||||
y="-1.0243618"
|
||||
width="1.6802826"
|
||||
height="3.0487237">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.3187927"
|
||||
id="feGaussianBlur54" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter55"
|
||||
x="-0.33171228"
|
||||
y="-1.092941"
|
||||
width="1.6634245"
|
||||
height="3.1858823">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.4263355"
|
||||
id="feGaussianBlur55" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter56"
|
||||
x="-0.3459667"
|
||||
y="-0.98818076"
|
||||
width="1.6919334"
|
||||
height="2.9763615">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.9267071"
|
||||
id="feGaussianBlur56" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter57"
|
||||
x="-0.50604737"
|
||||
y="-0.53225249"
|
||||
width="2.0120947"
|
||||
height="2.0645051">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5.8261111"
|
||||
id="feGaussianBlur57" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter58"
|
||||
x="-0.32130077"
|
||||
y="-1.1307663"
|
||||
width="1.6426016"
|
||||
height="3.2615325">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.2390405"
|
||||
id="feGaussianBlur58" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter59"
|
||||
x="-0.4030115"
|
||||
y="-0.71156067"
|
||||
width="1.806023"
|
||||
height="2.4231212">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.4950086"
|
||||
id="feGaussianBlur59" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter60"
|
||||
x="-0.2985791"
|
||||
y="-1.7272615"
|
||||
width="1.5971582"
|
||||
height="4.4545231">
|
||||
<feGaussianBlur
|
||||
stdDeviation="8.1470038"
|
||||
id="feGaussianBlur60" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter70"
|
||||
x="-0.44225678"
|
||||
y="-0.3197549"
|
||||
width="1.8841637"
|
||||
height="1.6395177">
|
||||
<feGaussianBlur
|
||||
stdDeviation="18.461288"
|
||||
id="feGaussianBlur70" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter72"
|
||||
x="-0.43271318"
|
||||
y="-0.31310251"
|
||||
width="1.8654264"
|
||||
height="1.626205">
|
||||
<feGaussianBlur
|
||||
stdDeviation="18.509006"
|
||||
id="feGaussianBlur72" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter1"
|
||||
x="-0.45705071"
|
||||
y="-0.57220238"
|
||||
width="1.9141014"
|
||||
height="2.1444046">
|
||||
<feGaussianBlur
|
||||
stdDeviation="8.5810337"
|
||||
id="feGaussianBlur1" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter2"
|
||||
x="-0.545201"
|
||||
y="-0.88686216"
|
||||
width="2.0904019"
|
||||
height="2.7737243">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4.3199889"
|
||||
id="feGaussianBlur2" />
|
||||
</filter>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath1">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path5" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath5">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path8" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath8">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path11" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath11">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path12" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath12">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 452.78682,224.36663 q -8.98432,0 -12.80266,-4.94138 -3.59373,-5.16598 -4.26757,-12.80265 -0.67381,-7.86128 -0.67381,-15.72256 0,-11.90423 -3.36913,-19.09168 -3.36912,-7.41207 -11.90421,-7.41207 -7.63667,0 -11.2304,7.41207 -3.59373,7.18745 -3.59373,19.09168 0,7.86128 -0.67384,15.49795 -0.44921,7.63667 -4.04294,12.80266 -3.59373,4.94137 -13.02726,4.94137 -6.96286,0 -11.67961,-5.16598 -4.49217,-5.16599 -6.96285,-13.47648 -2.47068,-8.3105 -3.59373,-17.96864 -0.89842,-9.65815 -0.89842,-18.64247 0,-8.98432 0.89842,-18.41785 0.89844,-9.43354 3.14452,-17.51943 2.47068,-8.08588 6.96285,-13.02726 4.49215,-4.94138 11.67961,-4.94138 12.80266,0 15.94715,11.67962 2.69531,-2.9199 8.53512,-5.39059 6.0644,-2.6953 13.70107,-2.6953 17.29484,0 28.30063,8.75971 11.00577,8.75972 15.94715,24.25767 5.16599,15.27334 4.94139,35.48806 0,10.33197 -2.02147,18.86707 -1.79687,8.3105 -6.51365,13.47648 -4.49215,4.94138 -12.80263,4.94138 z"
|
||||
id="path14" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath15">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 511.63431,224.36663 q -8.98433,0 -13.47647,-5.16598 -4.26755,-5.16599 -5.83981,-15.72256 -1.34765,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34763,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49214,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49217,4.94137 6.06443,15.72256 1.57223,10.78118 1.57223,27.62678 0.22461,16.8456 -1.34762,27.62679 -1.57226,10.55657 -6.06444,15.72256 -4.49215,5.16598 -13.47647,5.16598 z"
|
||||
id="path16" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath17">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 681.32926,224.3022 q -8.98433,0 -13.47647,-5.16597 -4.26755,-5.16599 -5.83981,-15.72257 -1.34765,-10.78118 -1.57226,-27.62678 0,-16.8456 1.34763,-27.62678 1.57226,-10.78119 6.06444,-15.72257 4.49214,-5.16598 13.47647,-5.16598 8.98432,0 13.25187,5.16598 4.49217,4.94138 6.06443,15.72257 1.57223,10.78118 1.57223,27.62678 0.22461,16.8456 -1.34762,27.62678 -1.57226,10.55658 -6.06444,15.72257 -4.49215,5.16597 -13.47647,5.16597 z"
|
||||
id="path17" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath18">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path19" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath19">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path20" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath20">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path21" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath21">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 573.177,224.36663 q -7.63667,0 -12.57805,-4.94138 -4.71678,-5.16598 -7.18746,-13.70108 -2.47068,-8.53511 -3.36913,-19.5409 -0.89842,-11.00579 -0.89842,-22.68541 0,-7.86128 0.22461,-15.94717 0.44921,-8.31049 2.24607,-15.27334 2.02147,-6.96285 6.51362,-11.2304 4.71678,-4.49216 13.25187,-4.49216 6.06443,0 11.00582,2.9199 4.94136,2.6953 6.28901,8.3105 5.16599,-4.71677 13.02727,-6.73824 8.08587,-2.24608 16.62099,-2.24608 7.18746,0 14.59952,3.59373 7.41207,3.36912 12.57803,9.43353 5.16599,6.06442 5.16599,13.70109 0,6.51363 -4.94136,12.35344 -4.71678,5.83981 -13.4765,5.83981 -3.81831,0 -6.28901,-0.89843 -2.47068,-1.12304 -4.71676,-2.02147 -2.2461,-0.89844 -5.39059,-0.89844 -8.31051,0 -15.27337,6.73824 -6.73822,6.51364 -6.73822,17.74404 0,5.8398 -0.44921,12.80265 -0.22463,6.73824 -2.02147,13.02727 -1.57226,6.28902 -5.83983,10.33196 -4.26754,3.81834 -12.35342,3.81834 z"
|
||||
id="path22" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath23">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 681.43807,224.36663 q -8.98432,0 -13.47647,-5.16598 -4.26757,-5.16599 -5.83983,-15.72256 -1.34763,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34765,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49215,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49215,4.94137 6.06441,15.72256 1.57226,10.78118 1.57226,27.62678 0.2246,16.8456 -1.34766,27.62679 -1.57223,10.55657 -6.06441,15.72256 -4.49215,5.16598 -13.47647,5.16598 z"
|
||||
id="path25" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath25">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 681.43807,224.36663 q -8.98432,0 -13.47647,-5.16598 -4.26757,-5.16599 -5.83983,-15.72256 -1.34763,-10.78119 -1.57226,-27.62679 0,-16.8456 1.34765,-27.62678 1.57226,-10.78119 6.06444,-15.72256 4.49215,-5.16599 13.47647,-5.16599 8.98432,0 13.25187,5.16599 4.49215,4.94137 6.06441,15.72256 1.57226,10.78118 1.57226,27.62678 0.2246,16.8456 -1.34766,27.62679 -1.57223,10.55657 -6.06441,15.72256 -4.49215,5.16598 -13.47647,5.16598 z"
|
||||
id="path26" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath34">
|
||||
<path
|
||||
id="path35"
|
||||
style="fill:#d55c44;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 169.33334,220.13334 c 0,8.46667 -4.23334,16.93333 -33.86667,16.93333 -29.63333,0 -33.86667,-8.46666 -33.86667,-16.93333 0,-12.7 8.46667,-21.16667 33.86667,-21.16667 25.4,0 33.86667,8.46667 33.86667,21.16667 z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath35">
|
||||
<path
|
||||
id="path36"
|
||||
style="fill:#d55c44;fill-opacity:1;stroke-width:2.11667;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 715.91343,141.3418 c 0,8.46667 -4.23334,16.93332 -33.86667,16.93332 -29.63333,0 -33.86667,-8.46665 -33.86667,-16.93332 0,-12.7 8.46667,-21.16668 33.86667,-21.16668 25.4,0 33.86667,8.46668 33.86667,21.16668 z" />
|
||||
</clipPath>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter61"
|
||||
x="-0.43233621"
|
||||
y="-0.31282973"
|
||||
width="1.8646724"
|
||||
height="1.6256595">
|
||||
<feGaussianBlur
|
||||
stdDeviation="18.492881"
|
||||
id="feGaussianBlur61" />
|
||||
</filter>
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(151.57,-212.41655)">
|
||||
<g
|
||||
id="g69"
|
||||
transform="matrix(1.1313091,0,0,1.1313091,-521.46052,171.60178)"
|
||||
style="fill:#d55c44;fill-opacity:1">
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:0.673514;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 427.944,81.955473 q -2.85876,0 -4.28814,-1.64378 -1.35793,-1.6438 -1.85821,-5.00284 -0.42881,-3.43053 -0.50028,-8.79071 0,-5.36019 0.42881,-8.79071 0.50029,-3.43053 1.92968,-5.00285 1.42938,-1.64379 4.28814,-1.64379 2.85877,0 4.21669,1.64379 1.42938,1.57232 1.92966,5.00285 0.50029,3.43052 0.50029,8.79071 0.0715,5.36018 -0.42882,8.79071 -0.50028,3.35904 -1.92966,5.00284 -1.42939,1.64378 -4.28816,1.64378 z"
|
||||
id="path63" />
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:0.673514;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 393.49586,81.955473 q -2.42995,0 -4.00228,-1.57232 -1.50085,-1.64379 -2.28701,-4.35961 -0.78616,-2.71584 -1.07204,-6.21782 -0.28588,-3.50199 -0.28588,-7.21839 0,-2.50142 0.0715,-5.07431 0.14294,-2.64436 0.71469,-4.8599 0.64323,-2.21555 2.07261,-3.57346 1.50085,-1.42938 4.21668,-1.42938 1.92967,0 3.50199,0.92909 1.57232,0.85763 2.00114,2.64436 1.64378,-1.50085 4.1452,-2.14407 2.5729,-0.71469 5.28873,-0.71469 2.28701,0 4.6455,1.1435 2.35848,1.07204 4.00227,3.00171 1.64379,1.92966 1.64379,4.35962 0,2.0726 -1.57233,3.9308 -1.50085,1.8582 -4.28814,1.8582 -1.21498,0 -2.00114,-0.28588 -0.78616,-0.35734 -1.50086,-0.64322 -0.71469,-0.28588 -1.71525,-0.28588 -2.64436,0 -4.85991,2.14408 -2.14408,2.07261 -2.14408,5.64607 0,1.85819 -0.14293,4.07373 -0.0715,2.14408 -0.64322,4.14522 -0.50029,2.00114 -1.8582,3.28758 -1.35792,1.21497 -3.9308,1.21497 z"
|
||||
id="path64" />
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:0.673514;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 373.91327,81.955473 q -2.85877,0 -4.28815,-1.64378 -1.35791,-1.6438 -1.8582,-5.00284 -0.42881,-3.43053 -0.50028,-8.79071 0,-5.36019 0.42881,-8.79071 0.50029,-3.43053 1.92967,-5.00285 1.42938,-1.64379 4.28815,-1.64379 2.85877,0 4.21668,1.64379 1.42938,1.57232 1.92967,5.00285 0.50029,3.43052 0.50029,8.79071 0.0715,5.36018 -0.42882,8.79071 -0.50029,3.35904 -1.92967,5.00284 -1.42938,1.64378 -4.28815,1.64378 z"
|
||||
id="path65" />
|
||||
<path
|
||||
style="font-size:224.608px;line-height:393.415px;font-family:'Cherry Bomb One';-inkscape-font-specification:'Cherry Bomb One';letter-spacing:0px;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:0.673514;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 355.18829,81.955473 q -2.85878,0 -4.07375,-1.57232 -1.14351,-1.64379 -1.35791,-4.07374 -0.21441,-2.50142 -0.21441,-5.00284 0,-3.78787 -1.07204,-6.07488 -1.07204,-2.35848 -3.78786,-2.35848 -2.42996,0 -3.57347,2.35848 -1.1435,2.28701 -1.1435,6.07488 0,2.50142 -0.21441,4.93137 -0.14294,2.42995 -1.28644,4.07375 -1.14351,1.57232 -4.14522,1.57232 -2.21554,0 -3.71639,-1.64379 -1.42939,-1.6438 -2.21554,-4.28815 -0.78617,-2.64437 -1.14352,-5.71754 -0.28587,-3.07317 -0.28587,-5.93194 0,-2.85877 0.28587,-5.86047 0.28588,-3.0017 1.00058,-5.5746 0.78616,-2.57289 2.21554,-4.14521 1.42938,-1.57232 3.71639,-1.57232 4.07375,0 5.07432,3.7164 0.85763,-0.9291 2.71583,-1.71526 1.92967,-0.85763 4.35962,-0.85763 5.50312,0 9.00511,2.78729 3.50199,2.78731 5.07431,7.71868 1.64379,4.8599 1.57233,11.29212 0,3.28759 -0.64323,6.00341 -0.57175,2.64436 -2.07261,4.28815 -1.42938,1.57232 -4.07373,1.57232 z"
|
||||
id="path66" />
|
||||
<path
|
||||
style="display:inline;mix-blend-mode:normal;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:4.02897;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 101.6,42.333334 c 16.93334,0 84.66667,50.800005 84.66667,93.133336 0,28.57209 -13.39181,41.07402 -25.11117,46.54433 -8.87807,4.14406 -11.33775,-1.382 -6.3999,-9.88879 3.31767,-5.71559 6.11107,-12.63293 6.11107,-19.72221 0,-12.7 -4.23333,-21.16666 -12.7,-29.63333 -8.46667,-8.46667 -19.34216,-12.70001 -25.4,-12.7 -8.46666,1e-5 -12.7,24.8856 -12.7,38.1 0,10.52973 3.14292,20.01182 6.30176,26.88277 2.97912,6.48004 0.48516,10.93086 -6.19604,8.47352 -7.29956,-2.68477 -16.052373,-7.98293 -21.272388,-18.42296 -8.466666,-16.93333 -4.233333,-41.3022 -4.233333,-63.5 0,-33.866665 -7e-6,-59.266666 16.933331,-59.266666 z"
|
||||
id="path61"
|
||||
transform="matrix(0.16716789,0,0,0.16716789,351.42847,16.594243)" />
|
||||
<path
|
||||
style="display:inline;mix-blend-mode:normal;fill:#d55c44;fill-opacity:1;stroke:none;stroke-width:4.02897;stroke-linecap:square;paint-order:stroke fill markers"
|
||||
d="m 101.6,42.333334 c 16.93334,0 84.66667,50.800005 84.66667,93.133336 0,28.57209 -13.39181,41.07402 -25.11117,46.54433 -8.87807,4.14406 -11.33775,-1.382 -6.3999,-9.88879 3.31767,-5.71559 6.11107,-12.63293 6.11107,-19.72221 0,-12.7 -4.23333,-21.16666 -12.7,-29.63333 -8.46667,-8.46667 -19.34216,-12.70001 -25.4,-12.7 -8.46666,1e-5 -12.7,24.8856 -12.7,38.1 0,10.52973 3.14292,20.01182 6.30176,26.88277 2.97912,6.48004 0.48516,10.93086 -6.19604,8.47352 -7.29956,-2.68477 -16.052373,-7.98293 -21.272388,-18.42296 -8.466666,-16.93333 -4.233333,-41.3022 -4.233333,-63.5 0,-33.866665 -7e-6,-59.266666 16.933331,-59.266666 z"
|
||||
id="path62"
|
||||
transform="matrix(0.16716789,0,0,0.16716789,405.30952,16.594243)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 28 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 55 KiB |
Generated
+6
-6
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1752077645,
|
||||
"narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
|
||||
"lastModified": 1757967192,
|
||||
"narHash": "sha256-/aA9A/OBmnuOMgwfzdsXRusqzUpd8rQnQY8jtrHK+To=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
|
||||
"rev": "0d7c15863b251a7a50265e57c1dca1a7add2e291",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -29,11 +29,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1752374969,
|
||||
"narHash": "sha256-Ky3ynEkJXih7mvWyt9DWoiSiZGqPeHLU1tlBU4b0mcc=",
|
||||
"lastModified": 1757989933,
|
||||
"narHash": "sha256-9cpKYWWPCFhgwQTww8S94rTXgg8Q8ydFv9fXM6I8xQM=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "75fb000638e6d0f57cb1e8b7a4550cbdd8c76f1d",
|
||||
"rev": "8249aa3442fb9b45e615a35f39eca2fe5510d7c3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
rust-overlay,
|
||||
}:
|
||||
let
|
||||
revision = self.shortRev or self.dirtyShortRev or "unknown";
|
||||
niri-package =
|
||||
{
|
||||
lib,
|
||||
@@ -46,7 +47,7 @@
|
||||
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "niri";
|
||||
version = self.shortRev or self.dirtyShortRev or "unknown";
|
||||
version = revision;
|
||||
|
||||
src = lib.fileset.toSource {
|
||||
root = ./.;
|
||||
@@ -64,7 +65,7 @@
|
||||
postPatch = ''
|
||||
patchShebangs resources/niri-session
|
||||
substituteInPlace resources/niri.service \
|
||||
--replace-fail '/usr/bin' "$out/bin"
|
||||
--replace-fail 'ExecStart=niri' "ExecStart=$out/bin/niri"
|
||||
'';
|
||||
|
||||
cargoLock = {
|
||||
@@ -107,7 +108,7 @@
|
||||
buildNoDefaultFeatures = true;
|
||||
|
||||
# ever since this commit:
|
||||
# https://github.com/YaLTeR/niri/commit/771ea1e81557ffe7af9cbdbec161601575b64d81
|
||||
# https://github.com/niri-wm/niri/commit/771ea1e81557ffe7af9cbdbec161601575b64d81
|
||||
# niri now runs an actual instance of the real compositor (with a mock backend) during tests
|
||||
# and thus creates a real socket file in the runtime dir.
|
||||
# this is fine for our build, we just need to make sure it has a directory to write to.
|
||||
@@ -126,6 +127,7 @@
|
||||
installShellCompletion --cmd niri \
|
||||
--bash <($out/bin/niri completions bash) \
|
||||
--fish <($out/bin/niri completions fish) \
|
||||
--nushell <($out/bin/niri completions nushell) \
|
||||
--zsh <($out/bin/niri completions zsh)
|
||||
|
||||
install -Dm644 resources/niri.desktop -t $out/share/wayland-sessions
|
||||
@@ -147,6 +149,7 @@
|
||||
"-Wl,--pop-state"
|
||||
]
|
||||
);
|
||||
NIRI_BUILD_COMMIT = revision;
|
||||
};
|
||||
|
||||
passthru = {
|
||||
@@ -155,7 +158,7 @@
|
||||
|
||||
meta = {
|
||||
description = "Scrollable-tiling Wayland compositor";
|
||||
homepage = "https://github.com/YaLTeR/niri";
|
||||
homepage = "https://github.com/niri-wm/niri";
|
||||
license = lib.licenses.gpl3Only;
|
||||
mainProgram = "niri";
|
||||
platforms = lib.platforms.linux;
|
||||
|
||||
@@ -9,11 +9,11 @@ repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
csscolorparser = "0.7.2"
|
||||
csscolorparser = "0.8.3"
|
||||
knuffel = "3.2.0"
|
||||
miette = { version = "5.10.0", features = ["fancy-no-backtrace"] }
|
||||
niri-ipc = { version = "25.8.0", path = "../niri-ipc" }
|
||||
regex = "1.11.2"
|
||||
niri-ipc = { version = "26.4.0", path = "../niri-ipc" }
|
||||
regex = "1.12.3"
|
||||
smithay = { workspace = true, features = ["backend_libinput"] }
|
||||
tracing.workspace = true
|
||||
tracy-client.workspace = true
|
||||
@@ -21,3 +21,4 @@ tracy-client.workspace = true
|
||||
[dev-dependencies]
|
||||
insta.workspace = true
|
||||
pretty_assertions = "1.4.1"
|
||||
diff = "0.1.13"
|
||||
|
||||
@@ -1,42 +1,31 @@
|
||||
use knuffel::errors::DecodeError;
|
||||
use knuffel::Decode as _;
|
||||
|
||||
use crate::utils::{expect_only_children, parse_arg_node};
|
||||
use crate::utils::{expect_only_children, parse_arg_node, MergeWith};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Animations {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = FloatOrInt(1.))]
|
||||
pub slowdown: FloatOrInt<0, { i32::MAX }>,
|
||||
#[knuffel(child, default)]
|
||||
pub slowdown: f64,
|
||||
pub workspace_switch: WorkspaceSwitchAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_open: WindowOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_close: WindowCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub horizontal_view_movement: HorizontalViewMovementAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_movement: WindowMovementAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_resize: WindowResizeAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub exit_confirmation_open_close: ExitConfirmationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub screenshot_ui_open: ScreenshotUiOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub overview_open_close: OverviewOpenCloseAnim,
|
||||
pub recent_windows_close: RecentWindowsCloseAnim,
|
||||
}
|
||||
|
||||
impl Default for Animations {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
slowdown: FloatOrInt(1.),
|
||||
slowdown: 1.,
|
||||
workspace_switch: Default::default(),
|
||||
horizontal_view_movement: Default::default(),
|
||||
window_movement: Default::default(),
|
||||
@@ -47,10 +36,71 @@ impl Default for Animations {
|
||||
exit_confirmation_open_close: Default::default(),
|
||||
screenshot_ui_open: Default::default(),
|
||||
overview_open_close: Default::default(),
|
||||
recent_windows_close: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct AnimationsPart {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub slowdown: Option<FloatOrInt<0, { i32::MAX }>>,
|
||||
#[knuffel(child)]
|
||||
pub workspace_switch: Option<WorkspaceSwitchAnim>,
|
||||
#[knuffel(child)]
|
||||
pub window_open: Option<WindowOpenAnim>,
|
||||
#[knuffel(child)]
|
||||
pub window_close: Option<WindowCloseAnim>,
|
||||
#[knuffel(child)]
|
||||
pub horizontal_view_movement: Option<HorizontalViewMovementAnim>,
|
||||
#[knuffel(child)]
|
||||
pub window_movement: Option<WindowMovementAnim>,
|
||||
#[knuffel(child)]
|
||||
pub window_resize: Option<WindowResizeAnim>,
|
||||
#[knuffel(child)]
|
||||
pub config_notification_open_close: Option<ConfigNotificationOpenCloseAnim>,
|
||||
#[knuffel(child)]
|
||||
pub exit_confirmation_open_close: Option<ExitConfirmationOpenCloseAnim>,
|
||||
#[knuffel(child)]
|
||||
pub screenshot_ui_open: Option<ScreenshotUiOpenAnim>,
|
||||
#[knuffel(child)]
|
||||
pub overview_open_close: Option<OverviewOpenCloseAnim>,
|
||||
#[knuffel(child)]
|
||||
pub recent_windows_close: Option<RecentWindowsCloseAnim>,
|
||||
}
|
||||
|
||||
impl MergeWith<AnimationsPart> for Animations {
|
||||
fn merge_with(&mut self, part: &AnimationsPart) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge!((self, part), slowdown);
|
||||
|
||||
// Animation properties are fairly tied together, except maybe `off`. So let's just save
|
||||
// ourselves the work and not merge within individual animations.
|
||||
merge_clone!(
|
||||
(self, part),
|
||||
workspace_switch,
|
||||
window_open,
|
||||
window_close,
|
||||
horizontal_view_movement,
|
||||
window_movement,
|
||||
window_resize,
|
||||
config_notification_open_close,
|
||||
exit_confirmation_open_close,
|
||||
screenshot_ui_open,
|
||||
overview_open_close,
|
||||
recent_windows_close,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Animation {
|
||||
pub off: bool,
|
||||
@@ -260,6 +310,22 @@ impl Default for OverviewOpenCloseAnim {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct RecentWindowsCloseAnim(pub Animation);
|
||||
|
||||
impl Default for RecentWindowsCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for WorkspaceSwitchAnim
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
@@ -443,6 +509,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for RecentWindowsCloseAnim
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
let default = Self::default().0;
|
||||
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
pub fn new_off() -> Self {
|
||||
Self {
|
||||
|
||||
+458
-249
@@ -5,6 +5,7 @@ use knuffel::errors::DecodeError;
|
||||
use miette::{miette, IntoDiagnostic as _};
|
||||
use smithay::backend::renderer::Color32F;
|
||||
|
||||
use crate::utils::{Flag, MergeWith};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.25, 0.25, 0.25, 1.]);
|
||||
@@ -76,6 +77,12 @@ impl MulAssign<f32> for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Color32F {
|
||||
fn from(value: Color) -> Self {
|
||||
Color32F::from(value.to_array_premul())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Gradient {
|
||||
#[knuffel(property, str)]
|
||||
@@ -221,23 +228,15 @@ impl CornerRadius {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FocusRing {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
||||
pub width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default = Self::default().active_color)]
|
||||
pub width: f64,
|
||||
pub active_color: Color,
|
||||
#[knuffel(child, default = Self::default().inactive_color)]
|
||||
pub inactive_color: Color,
|
||||
#[knuffel(child, default = Self::default().urgent_color)]
|
||||
pub urgent_color: Color,
|
||||
#[knuffel(child)]
|
||||
pub active_gradient: Option<Gradient>,
|
||||
#[knuffel(child)]
|
||||
pub inactive_gradient: Option<Gradient>,
|
||||
#[knuffel(child)]
|
||||
pub urgent_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
@@ -245,7 +244,7 @@ impl Default for FocusRing {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
width: FloatOrInt(4.),
|
||||
width: 4.,
|
||||
active_color: Color::from_rgba8_unpremul(127, 200, 255, 255),
|
||||
inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
|
||||
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
|
||||
@@ -256,23 +255,15 @@ impl Default for FocusRing {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Border {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
||||
pub width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default = Self::default().active_color)]
|
||||
pub width: f64,
|
||||
pub active_color: Color,
|
||||
#[knuffel(child, default = Self::default().inactive_color)]
|
||||
pub inactive_color: Color,
|
||||
#[knuffel(child, default = Self::default().urgent_color)]
|
||||
pub urgent_color: Color,
|
||||
#[knuffel(child)]
|
||||
pub active_gradient: Option<Gradient>,
|
||||
#[knuffel(child)]
|
||||
pub inactive_gradient: Option<Gradient>,
|
||||
#[knuffel(child)]
|
||||
pub urgent_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
@@ -280,7 +271,7 @@ impl Default for Border {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: true,
|
||||
width: FloatOrInt(4.),
|
||||
width: 4.,
|
||||
active_color: Color::from_rgba8_unpremul(255, 200, 127, 255),
|
||||
inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
|
||||
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
|
||||
@@ -321,21 +312,40 @@ impl From<FocusRing> for Border {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
impl MergeWith<BorderRule> for Border {
|
||||
fn merge_with(&mut self, part: &BorderRule) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge!((self, part), width);
|
||||
|
||||
merge_color_gradient!(
|
||||
(self, part),
|
||||
(active_color, active_gradient),
|
||||
(inactive_color, inactive_gradient),
|
||||
(urgent_color, urgent_gradient),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<BorderRule> for FocusRing {
|
||||
fn merge_with(&mut self, part: &BorderRule) {
|
||||
let mut x = Border::from(*self);
|
||||
x.merge_with(part);
|
||||
*self = FocusRing::from(x);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Shadow {
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child, default = Self::default().offset)]
|
||||
pub offset: ShadowOffset,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().softness)]
|
||||
pub softness: FloatOrInt<0, 1024>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().spread)]
|
||||
pub spread: FloatOrInt<-1024, 1024>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().draw_behind_window)]
|
||||
pub softness: f64,
|
||||
pub spread: f64,
|
||||
pub draw_behind_window: bool,
|
||||
#[knuffel(child, default = Self::default().color)]
|
||||
pub color: Color,
|
||||
#[knuffel(child)]
|
||||
pub inactive_color: Option<Color>,
|
||||
}
|
||||
|
||||
@@ -347,15 +357,30 @@ impl Default for Shadow {
|
||||
x: FloatOrInt(0.),
|
||||
y: FloatOrInt(5.),
|
||||
},
|
||||
softness: FloatOrInt(30.),
|
||||
spread: FloatOrInt(5.),
|
||||
softness: 30.,
|
||||
spread: 5.,
|
||||
draw_behind_window: false,
|
||||
color: Color::from_rgba8_unpremul(0, 0, 0, 0x70),
|
||||
color: Color::from_rgba8_unpremul(0, 0, 0, 0x77),
|
||||
inactive_color: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<ShadowRule> for Shadow {
|
||||
fn merge_with(&mut self, part: &ShadowRule) {
|
||||
self.on |= part.on;
|
||||
if part.off {
|
||||
self.on = false;
|
||||
}
|
||||
|
||||
merge!((self, part), softness, spread);
|
||||
|
||||
merge_clone!((self, part), offset, draw_behind_window, color);
|
||||
|
||||
merge_clone_opt!((self, part), inactive_color);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ShadowOffset {
|
||||
#[knuffel(property, default)]
|
||||
@@ -364,17 +389,12 @@ pub struct ShadowOffset {
|
||||
pub y: FloatOrInt<-65535, 65535>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct WorkspaceShadow {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, default = Self::default().offset)]
|
||||
pub offset: ShadowOffset,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().softness)]
|
||||
pub softness: FloatOrInt<0, 1024>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().spread)]
|
||||
pub spread: FloatOrInt<-1024, 1024>,
|
||||
#[knuffel(child, default = Self::default().color)]
|
||||
pub softness: f64,
|
||||
pub spread: f64,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
@@ -386,8 +406,8 @@ impl Default for WorkspaceShadow {
|
||||
x: FloatOrInt(0.),
|
||||
y: FloatOrInt(10.),
|
||||
},
|
||||
softness: FloatOrInt(40.),
|
||||
spread: FloatOrInt(10.),
|
||||
softness: 40.,
|
||||
spread: 10.,
|
||||
color: Color::from_rgba8_unpremul(0, 0, 0, 0x50),
|
||||
}
|
||||
}
|
||||
@@ -408,25 +428,126 @@ impl From<WorkspaceShadow> for Shadow {
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicator {
|
||||
pub struct WorkspaceShadowPart {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub hide_when_single_tab: bool,
|
||||
pub on: bool,
|
||||
#[knuffel(child)]
|
||||
pub offset: Option<ShadowOffset>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub softness: Option<FloatOrInt<0, 1024>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub spread: Option<FloatOrInt<-1024, 1024>>,
|
||||
#[knuffel(child)]
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
impl MergeWith<WorkspaceShadowPart> for WorkspaceShadow {
|
||||
fn merge_with(&mut self, part: &WorkspaceShadowPart) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge_clone!((self, part), offset, color);
|
||||
merge!((self, part), softness, spread);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicator {
|
||||
pub off: bool,
|
||||
pub hide_when_single_tab: bool,
|
||||
pub place_within_column: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().gap)]
|
||||
pub gap: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
||||
pub width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default = Self::default().length)]
|
||||
pub gap: f64,
|
||||
pub width: f64,
|
||||
pub length: TabIndicatorLength,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().position)]
|
||||
pub position: TabIndicatorPosition,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().gaps_between_tabs)]
|
||||
pub gaps_between_tabs: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().corner_radius)]
|
||||
pub corner_radius: FloatOrInt<0, 65535>,
|
||||
pub gaps_between_tabs: f64,
|
||||
pub corner_radius: f64,
|
||||
pub active_color: Option<Color>,
|
||||
pub inactive_color: Option<Color>,
|
||||
pub urgent_color: Option<Color>,
|
||||
pub active_gradient: Option<Gradient>,
|
||||
pub inactive_gradient: Option<Gradient>,
|
||||
pub urgent_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
impl Default for TabIndicator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
hide_when_single_tab: false,
|
||||
place_within_column: false,
|
||||
gap: 5.,
|
||||
width: 4.,
|
||||
length: TabIndicatorLength {
|
||||
total_proportion: Some(0.5),
|
||||
},
|
||||
position: TabIndicatorPosition::Left,
|
||||
gaps_between_tabs: 0.,
|
||||
corner_radius: 0.,
|
||||
active_color: None,
|
||||
inactive_color: None,
|
||||
urgent_color: None,
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
urgent_gradient: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<TabIndicatorPart> for TabIndicator {
|
||||
fn merge_with(&mut self, part: &TabIndicatorPart) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge!(
|
||||
(self, part),
|
||||
hide_when_single_tab,
|
||||
place_within_column,
|
||||
gap,
|
||||
width,
|
||||
gaps_between_tabs,
|
||||
corner_radius,
|
||||
);
|
||||
|
||||
merge_clone!((self, part), length, position);
|
||||
|
||||
merge_color_gradient_opt!(
|
||||
(self, part),
|
||||
(active_color, active_gradient),
|
||||
(inactive_color, inactive_gradient),
|
||||
(urgent_color, urgent_gradient),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicatorPart {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child)]
|
||||
pub hide_when_single_tab: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub place_within_column: Option<Flag>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub gap: Option<FloatOrInt<-65535, 65535>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub width: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child)]
|
||||
pub length: Option<TabIndicatorLength>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub position: Option<TabIndicatorPosition>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub gaps_between_tabs: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub corner_radius: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child)]
|
||||
pub active_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
@@ -441,30 +562,6 @@ pub struct TabIndicator {
|
||||
pub urgent_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
impl Default for TabIndicator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
hide_when_single_tab: false,
|
||||
place_within_column: false,
|
||||
gap: FloatOrInt(5.),
|
||||
width: FloatOrInt(4.),
|
||||
length: TabIndicatorLength {
|
||||
total_proportion: Some(0.5),
|
||||
},
|
||||
position: TabIndicatorPosition::Left,
|
||||
gaps_between_tabs: FloatOrInt(0.),
|
||||
corner_radius: FloatOrInt(0.),
|
||||
active_color: None,
|
||||
inactive_color: None,
|
||||
urgent_color: None,
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
urgent_gradient: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicatorLength {
|
||||
#[knuffel(property)]
|
||||
@@ -479,13 +576,10 @@ pub enum TabIndicatorPosition {
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct InsertHint {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, default = Self::default().color)]
|
||||
pub color: Color,
|
||||
#[knuffel(child)]
|
||||
pub gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
@@ -499,6 +593,29 @@ impl Default for InsertHint {
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<InsertHintPart> for InsertHint {
|
||||
fn merge_with(&mut self, part: &InsertHintPart) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge_color_gradient!((self, part), (color, gradient));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct InsertHintPart {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child)]
|
||||
pub color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlockOutFrom {
|
||||
Screencast,
|
||||
@@ -563,157 +680,45 @@ pub struct TabIndicatorRule {
|
||||
pub urgent_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
impl BorderRule {
|
||||
pub fn merge_with(&mut self, other: &Self) {
|
||||
if other.off {
|
||||
self.off = true;
|
||||
self.on = false;
|
||||
}
|
||||
impl MergeWith<Self> for BorderRule {
|
||||
fn merge_with(&mut self, part: &Self) {
|
||||
merge_on_off!((self, part));
|
||||
|
||||
if other.on {
|
||||
self.off = false;
|
||||
self.on = true;
|
||||
}
|
||||
merge_clone_opt!((self, part), width);
|
||||
|
||||
if let Some(x) = other.width {
|
||||
self.width = Some(x);
|
||||
}
|
||||
if let Some(x) = other.active_color {
|
||||
self.active_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_color {
|
||||
self.inactive_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.urgent_color {
|
||||
self.urgent_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.active_gradient {
|
||||
self.active_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_gradient {
|
||||
self.inactive_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = other.urgent_gradient {
|
||||
self.urgent_gradient = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_against(&self, mut config: Border) -> Border {
|
||||
config.off |= self.off;
|
||||
if self.on {
|
||||
config.off = false;
|
||||
}
|
||||
|
||||
if let Some(x) = self.width {
|
||||
config.width = x;
|
||||
}
|
||||
if let Some(x) = self.active_color {
|
||||
config.active_color = x;
|
||||
config.active_gradient = None;
|
||||
}
|
||||
if let Some(x) = self.inactive_color {
|
||||
config.inactive_color = x;
|
||||
config.inactive_gradient = None;
|
||||
}
|
||||
if let Some(x) = self.urgent_color {
|
||||
config.urgent_color = x;
|
||||
config.urgent_gradient = None;
|
||||
}
|
||||
if let Some(x) = self.active_gradient {
|
||||
config.active_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = self.inactive_gradient {
|
||||
config.inactive_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = self.urgent_gradient {
|
||||
config.urgent_gradient = Some(x);
|
||||
}
|
||||
|
||||
config
|
||||
merge_color_gradient_opt!(
|
||||
(self, part),
|
||||
(active_color, active_gradient),
|
||||
(inactive_color, inactive_gradient),
|
||||
(urgent_color, urgent_gradient),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ShadowRule {
|
||||
pub fn merge_with(&mut self, other: &Self) {
|
||||
if other.off {
|
||||
self.off = true;
|
||||
self.on = false;
|
||||
}
|
||||
impl MergeWith<Self> for ShadowRule {
|
||||
fn merge_with(&mut self, part: &Self) {
|
||||
merge_on_off!((self, part));
|
||||
|
||||
if other.on {
|
||||
self.off = false;
|
||||
self.on = true;
|
||||
}
|
||||
|
||||
if let Some(x) = other.offset {
|
||||
self.offset = Some(x);
|
||||
}
|
||||
if let Some(x) = other.softness {
|
||||
self.softness = Some(x);
|
||||
}
|
||||
if let Some(x) = other.spread {
|
||||
self.spread = Some(x);
|
||||
}
|
||||
if let Some(x) = other.draw_behind_window {
|
||||
self.draw_behind_window = Some(x);
|
||||
}
|
||||
if let Some(x) = other.color {
|
||||
self.color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_color {
|
||||
self.inactive_color = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_against(&self, mut config: Shadow) -> Shadow {
|
||||
config.on |= self.on;
|
||||
if self.off {
|
||||
config.on = false;
|
||||
}
|
||||
|
||||
if let Some(x) = self.offset {
|
||||
config.offset = x;
|
||||
}
|
||||
if let Some(x) = self.softness {
|
||||
config.softness = x;
|
||||
}
|
||||
if let Some(x) = self.spread {
|
||||
config.spread = x;
|
||||
}
|
||||
if let Some(x) = self.draw_behind_window {
|
||||
config.draw_behind_window = x;
|
||||
}
|
||||
if let Some(x) = self.color {
|
||||
config.color = x;
|
||||
}
|
||||
if let Some(x) = self.inactive_color {
|
||||
config.inactive_color = Some(x);
|
||||
}
|
||||
|
||||
config
|
||||
merge_clone_opt!(
|
||||
(self, part),
|
||||
offset,
|
||||
softness,
|
||||
spread,
|
||||
draw_behind_window,
|
||||
color,
|
||||
inactive_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl TabIndicatorRule {
|
||||
pub fn merge_with(&mut self, other: &Self) {
|
||||
if let Some(x) = other.active_color {
|
||||
self.active_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_color {
|
||||
self.inactive_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.urgent_color {
|
||||
self.urgent_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.active_gradient {
|
||||
self.active_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_gradient {
|
||||
self.inactive_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = other.urgent_gradient {
|
||||
self.urgent_gradient = Some(x);
|
||||
}
|
||||
impl MergeWith<Self> for TabIndicatorRule {
|
||||
fn merge_with(&mut self, part: &Self) {
|
||||
merge_color_gradient_opt!(
|
||||
(self, part),
|
||||
(active_color, active_gradient),
|
||||
(inactive_color, inactive_gradient),
|
||||
(urgent_color, urgent_gradient),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1001,11 +1006,109 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Blur {
|
||||
pub off: bool,
|
||||
pub passes: u8,
|
||||
pub offset: f64,
|
||||
pub noise: f64,
|
||||
pub saturation: f64,
|
||||
}
|
||||
|
||||
impl Default for Blur {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
passes: 3,
|
||||
offset: 3.,
|
||||
noise: 0.02,
|
||||
saturation: 1.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BlurPart {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub passes: Option<u8>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub offset: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub noise: Option<FloatOrInt<0, 1000>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub saturation: Option<FloatOrInt<0, 1000>>,
|
||||
}
|
||||
|
||||
impl MergeWith<BlurPart> for Blur {
|
||||
fn merge_with(&mut self, part: &BlurPart) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge_clone!((self, part), passes);
|
||||
merge!((self, part), offset, noise, saturation);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BackgroundEffectRule {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub xray: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub blur: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub noise: Option<FloatOrInt<0, 1000>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub saturation: Option<FloatOrInt<0, 1000>>,
|
||||
}
|
||||
|
||||
/// Resolved background effect rule.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BackgroundEffect {
|
||||
/// Whether to render with xray effect (see through).
|
||||
///
|
||||
/// - `None`: xray if any background effect is active
|
||||
/// - `Some(false)`: no xray
|
||||
/// - `Some(true)`: xray even if no other background effect is active
|
||||
pub xray: Option<bool>,
|
||||
|
||||
/// Whether to blur the background.
|
||||
///
|
||||
/// - `None`: blur when the window/layer requests it (e.g. through ext-background-effect
|
||||
/// protocol)
|
||||
/// - `Some(false)`: never blur
|
||||
/// - `Some(true)`: always blur
|
||||
pub blur: Option<bool>,
|
||||
|
||||
pub noise: Option<f64>,
|
||||
pub saturation: Option<f64>,
|
||||
}
|
||||
|
||||
impl MergeWith<BackgroundEffectRule> for BackgroundEffect {
|
||||
fn merge_with(&mut self, part: &BackgroundEffectRule) {
|
||||
merge_clone_opt!((self, part), xray, blur);
|
||||
|
||||
if let Some(x) = part.noise {
|
||||
self.noise = Some(x.0);
|
||||
}
|
||||
|
||||
if let Some(x) = part.saturation {
|
||||
self.saturation = Some(x.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
use insta::{assert_debug_snapshot, assert_snapshot};
|
||||
|
||||
use super::*;
|
||||
use crate::Config;
|
||||
|
||||
#[test]
|
||||
fn parse_gradient_interpolation() {
|
||||
@@ -1085,16 +1188,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_border_rule_on_off_merging() {
|
||||
fn is_on(config: &str, rules: &[&str]) -> String {
|
||||
let mut resolved = BorderRule {
|
||||
off: false,
|
||||
on: false,
|
||||
width: None,
|
||||
active_color: None,
|
||||
inactive_color: None,
|
||||
urgent_color: None,
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
urgent_gradient: None,
|
||||
let mut resolved = Border {
|
||||
off: config == "off",
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for rule in rules.iter().copied() {
|
||||
@@ -1107,17 +1203,7 @@ mod tests {
|
||||
resolved.merge_with(&rule);
|
||||
}
|
||||
|
||||
let config = Border {
|
||||
off: config == "off",
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if resolved.resolve_against(config).off {
|
||||
"off"
|
||||
} else {
|
||||
"on"
|
||||
}
|
||||
.to_owned()
|
||||
if resolved.off { "off" } else { "on" }.to_owned()
|
||||
}
|
||||
|
||||
assert_snapshot!(is_on("off", &[]), @"off");
|
||||
@@ -1140,4 +1226,127 @@ mod tests {
|
||||
assert_snapshot!(is_on("on", &["on", "off"]), @"off");
|
||||
assert_snapshot!(is_on("on", &["on", "on"]), @"on");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_color_can_override_base_gradient() {
|
||||
let config = Config::parse_mem(
|
||||
r##"
|
||||
// Start with gradient set.
|
||||
layout {
|
||||
border {
|
||||
active-gradient from="#101010" to="#202020"
|
||||
inactive-gradient from="#111111" to="#212121"
|
||||
urgent-gradient from="#121212" to="#222222"
|
||||
}
|
||||
}
|
||||
|
||||
// Override with color.
|
||||
window-rule {
|
||||
border {
|
||||
active-color "#abcdef"
|
||||
inactive-color "#123456"
|
||||
urgent-color "#fedcba"
|
||||
}
|
||||
}
|
||||
"##,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut border = config.layout.border;
|
||||
for rule in &config.window_rules {
|
||||
border.merge_with(&rule.border);
|
||||
}
|
||||
|
||||
// Gradient should be None because it's overwritten.
|
||||
assert_debug_snapshot!(
|
||||
(
|
||||
border.active_gradient.is_some(),
|
||||
border.inactive_gradient.is_some(),
|
||||
border.urgent_gradient.is_some(),
|
||||
),
|
||||
@r"
|
||||
(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_color_can_override_rule_gradient() {
|
||||
let config = Config::parse_mem(
|
||||
r##"
|
||||
// Start with gradient set.
|
||||
layout {
|
||||
border {
|
||||
active-gradient from="#101010" to="#202020"
|
||||
inactive-gradient from="#111111" to="#212121"
|
||||
urgent-gradient from="#121212" to="#222222"
|
||||
}
|
||||
}
|
||||
|
||||
// Window rule with gradients set.
|
||||
window-rule {
|
||||
border {
|
||||
active-gradient from="#303030" to="#404040"
|
||||
inactive-gradient from="#313131" to="#414141"
|
||||
urgent-gradient from="#323232" to="#424242"
|
||||
}
|
||||
|
||||
tab-indicator {
|
||||
active-gradient from="#505050" to="#606060"
|
||||
inactive-gradient from="#515151" to="#616161"
|
||||
urgent-gradient from="#525252" to="#626262"
|
||||
}
|
||||
}
|
||||
|
||||
// Override with color.
|
||||
window-rule {
|
||||
border {
|
||||
active-color "#abcdef"
|
||||
inactive-color "#123456"
|
||||
urgent-color "#fedcba"
|
||||
}
|
||||
|
||||
tab-indicator {
|
||||
active-color "#abcdef"
|
||||
inactive-color "#123456"
|
||||
urgent-color "#fedcba"
|
||||
}
|
||||
}
|
||||
"##,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut border = config.layout.border;
|
||||
let mut tab_indicator_rule = TabIndicatorRule::default();
|
||||
for rule in &config.window_rules {
|
||||
border.merge_with(&rule.border);
|
||||
tab_indicator_rule.merge_with(&rule.tab_indicator);
|
||||
}
|
||||
|
||||
// Gradient should be None because it's overwritten.
|
||||
assert_debug_snapshot!(
|
||||
(
|
||||
border.active_gradient.is_some(),
|
||||
border.inactive_gradient.is_some(),
|
||||
border.urgent_gradient.is_some(),
|
||||
tab_indicator_rule.active_gradient.is_some(),
|
||||
tab_indicator_rule.inactive_gradient.is_some(),
|
||||
tab_indicator_rule.urgent_gradient.is_some(),
|
||||
),
|
||||
@r"
|
||||
(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+154
-46
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -9,10 +10,11 @@ use niri_ipc::{
|
||||
ColumnDisplay, LayoutSwitchTarget, PositionChange, SizeChange, WorkspaceReferenceArg,
|
||||
};
|
||||
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
||||
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
||||
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE, KEYSYM_NO_FLAGS};
|
||||
use smithay::input::keyboard::Keysym;
|
||||
|
||||
use crate::utils::expect_only_children;
|
||||
use crate::recent_windows::{MruDirection, MruFilter, MruScope};
|
||||
use crate::utils::{expect_only_children, MergeWith};
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Binds(pub Vec<Bind>);
|
||||
@@ -77,6 +79,18 @@ pub struct SwitchBinds {
|
||||
pub tablet_mode_off: Option<SwitchAction>,
|
||||
}
|
||||
|
||||
impl MergeWith<SwitchBinds> for SwitchBinds {
|
||||
fn merge_with(&mut self, part: &SwitchBinds) {
|
||||
merge_clone_opt!(
|
||||
(self, part),
|
||||
lid_open,
|
||||
lid_close,
|
||||
tablet_mode_on,
|
||||
tablet_mode_off,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct SwitchAction {
|
||||
#[knuffel(child, unwrap(arguments))]
|
||||
@@ -106,16 +120,29 @@ pub enum Action {
|
||||
CancelScreenshot,
|
||||
#[knuffel(skip)]
|
||||
ScreenshotTogglePointer,
|
||||
Screenshot(#[knuffel(property(name = "show-pointer"), default = true)] bool),
|
||||
Screenshot(
|
||||
#[knuffel(property(name = "show-pointer"), default = true)] bool,
|
||||
// Path; not settable from knuffel
|
||||
Option<String>,
|
||||
),
|
||||
ScreenshotScreen(
|
||||
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
||||
#[knuffel(property(name = "show-pointer"), default = true)] bool,
|
||||
// Path; not settable from knuffel
|
||||
Option<String>,
|
||||
),
|
||||
ScreenshotWindow(
|
||||
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
||||
#[knuffel(property(name = "show-pointer"), default = false)] bool,
|
||||
// Path; not settable from knuffel
|
||||
Option<String>,
|
||||
),
|
||||
ScreenshotWindow(#[knuffel(property(name = "write-to-disk"), default = true)] bool),
|
||||
#[knuffel(skip)]
|
||||
ScreenshotWindowById {
|
||||
id: u64,
|
||||
write_to_disk: bool,
|
||||
show_pointer: bool,
|
||||
path: Option<String>,
|
||||
},
|
||||
ToggleKeyboardShortcutsInhibit,
|
||||
CloseWindow,
|
||||
@@ -291,6 +318,9 @@ pub enum Action {
|
||||
#[knuffel(skip)]
|
||||
SwitchPresetWindowHeightBackById(u64),
|
||||
MaximizeColumn,
|
||||
MaximizeWindowToEdges,
|
||||
#[knuffel(skip)]
|
||||
MaximizeWindowToEdgesById(u64),
|
||||
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
|
||||
ExpandColumnToAvailableWidth,
|
||||
SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget),
|
||||
@@ -327,6 +357,8 @@ pub enum Action {
|
||||
SetDynamicCastWindowById(u64),
|
||||
SetDynamicCastMonitor(#[knuffel(argument)] Option<String>),
|
||||
ClearDynamicCastTarget,
|
||||
#[knuffel(skip)]
|
||||
StopCast(u64),
|
||||
ToggleOverview,
|
||||
OpenOverview,
|
||||
CloseOverview,
|
||||
@@ -337,7 +369,27 @@ pub enum Action {
|
||||
#[knuffel(skip)]
|
||||
UnsetWindowUrgent(u64),
|
||||
#[knuffel(skip)]
|
||||
LoadConfigFile,
|
||||
LoadConfigFile(#[knuffel(argument)] Option<String>),
|
||||
#[knuffel(skip)]
|
||||
MruAdvance {
|
||||
direction: MruDirection,
|
||||
scope: Option<MruScope>,
|
||||
filter: Option<MruFilter>,
|
||||
},
|
||||
#[knuffel(skip)]
|
||||
MruConfirm,
|
||||
#[knuffel(skip)]
|
||||
MruCancel,
|
||||
#[knuffel(skip)]
|
||||
MruCloseCurrentWindow,
|
||||
#[knuffel(skip)]
|
||||
MruFirst,
|
||||
#[knuffel(skip)]
|
||||
MruLast,
|
||||
#[knuffel(skip)]
|
||||
MruSetScope(MruScope),
|
||||
#[knuffel(skip)]
|
||||
MruCycleScope,
|
||||
}
|
||||
|
||||
impl From<niri_ipc::Action> for Action {
|
||||
@@ -349,19 +401,31 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
||||
niri_ipc::Action::SpawnSh { command } => Self::SpawnSh(command),
|
||||
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
||||
niri_ipc::Action::Screenshot { show_pointer } => Self::Screenshot(show_pointer),
|
||||
niri_ipc::Action::Screenshot { show_pointer, path } => {
|
||||
Self::Screenshot(show_pointer, path)
|
||||
}
|
||||
niri_ipc::Action::ScreenshotScreen {
|
||||
write_to_disk,
|
||||
show_pointer,
|
||||
} => Self::ScreenshotScreen(write_to_disk, show_pointer),
|
||||
path,
|
||||
} => Self::ScreenshotScreen(write_to_disk, show_pointer, path),
|
||||
niri_ipc::Action::ScreenshotWindow {
|
||||
id: None,
|
||||
write_to_disk,
|
||||
} => Self::ScreenshotWindow(write_to_disk),
|
||||
show_pointer,
|
||||
path,
|
||||
} => Self::ScreenshotWindow(write_to_disk, show_pointer, path),
|
||||
niri_ipc::Action::ScreenshotWindow {
|
||||
id: Some(id),
|
||||
write_to_disk,
|
||||
} => Self::ScreenshotWindowById { id, write_to_disk },
|
||||
show_pointer,
|
||||
path,
|
||||
} => Self::ScreenshotWindowById {
|
||||
id,
|
||||
write_to_disk,
|
||||
show_pointer,
|
||||
path,
|
||||
},
|
||||
niri_ipc::Action::ToggleKeyboardShortcutsInhibit {} => {
|
||||
Self::ToggleKeyboardShortcutsInhibit
|
||||
}
|
||||
@@ -556,6 +620,10 @@ impl From<niri_ipc::Action> for Action {
|
||||
Self::SwitchPresetWindowHeightBackById(id)
|
||||
}
|
||||
niri_ipc::Action::MaximizeColumn {} => Self::MaximizeColumn,
|
||||
niri_ipc::Action::MaximizeWindowToEdges { id: None } => Self::MaximizeWindowToEdges,
|
||||
niri_ipc::Action::MaximizeWindowToEdges { id: Some(id) } => {
|
||||
Self::MaximizeWindowToEdgesById(id)
|
||||
}
|
||||
niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change),
|
||||
niri_ipc::Action::ExpandColumnToAvailableWidth {} => Self::ExpandColumnToAvailableWidth,
|
||||
niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout),
|
||||
@@ -625,13 +693,14 @@ impl From<niri_ipc::Action> for Action {
|
||||
Self::SetDynamicCastMonitor(output)
|
||||
}
|
||||
niri_ipc::Action::ClearDynamicCastTarget {} => Self::ClearDynamicCastTarget,
|
||||
niri_ipc::Action::StopCast { session_id } => Self::StopCast(session_id),
|
||||
niri_ipc::Action::ToggleOverview {} => Self::ToggleOverview,
|
||||
niri_ipc::Action::OpenOverview {} => Self::OpenOverview,
|
||||
niri_ipc::Action::CloseOverview {} => Self::CloseOverview,
|
||||
niri_ipc::Action::ToggleWindowUrgent { id } => Self::ToggleWindowUrgent(id),
|
||||
niri_ipc::Action::SetWindowUrgent { id } => Self::SetWindowUrgent(id),
|
||||
niri_ipc::Action::UnsetWindowUrgent { id } => Self::UnsetWindowUrgent(id),
|
||||
niri_ipc::Action::LoadConfigFile {} => Self::LoadConfigFile,
|
||||
niri_ipc::Action::LoadConfigFile { path } => Self::LoadConfigFile(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -701,7 +770,7 @@ where
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
expect_only_children(node, ctx);
|
||||
|
||||
let mut seen_keys = HashSet::new();
|
||||
let mut seen_keys: HashMap<Key, &knuffel::ast::SpannedNode<S>> = HashMap::new();
|
||||
|
||||
let mut binds = Vec::new();
|
||||
|
||||
@@ -711,39 +780,26 @@ where
|
||||
ctx.emit_error(e);
|
||||
}
|
||||
Ok(bind) => {
|
||||
if seen_keys.insert(bind.key) {
|
||||
binds.push(bind);
|
||||
} else {
|
||||
// ideally, this error should point to the previous instance of this keybind
|
||||
//
|
||||
// i (sodiboo) have tried to implement this in various ways:
|
||||
// miette!(), #[derive(Diagnostic)]
|
||||
// DecodeError::Custom, DecodeError::Conversion
|
||||
// nothing seems to work, and i suspect it's not possible.
|
||||
//
|
||||
// DecodeError is fairly restrictive.
|
||||
// even DecodeError::Custom just wraps a std::error::Error
|
||||
// and this erases all rich information from miette. (why???)
|
||||
//
|
||||
// why does knuffel do this?
|
||||
// from what i can tell, it doesn't even use DecodeError for much.
|
||||
// it only ever converts them to a Report anyways!
|
||||
// https://github.com/tailhook/knuffel/blob/c44c6b0c0f31ea6d1174d5d2ed41064922ea44ca/src/wrappers.rs#L55-L58
|
||||
//
|
||||
// besides like, allowing downstream users (such as us!)
|
||||
// to match on parse failure, i don't understand why
|
||||
// it doesn't just use a generic error type
|
||||
//
|
||||
// even the matching isn't consistent,
|
||||
// because errors can also be omitted as ctx.emit_error.
|
||||
// why does *that one* especially, require a DecodeError?
|
||||
//
|
||||
// anyways if you can make it format nicely, definitely do fix this
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"keybind",
|
||||
"duplicate keybind",
|
||||
));
|
||||
match seen_keys.entry(bind.key) {
|
||||
Entry::Occupied(entry) => {
|
||||
// Even though it's technically incorrect, we use
|
||||
// `DecodeError::Missing` here because it labels the bind with
|
||||
// "node starts here", which is the least bad option
|
||||
ctx.emit_error(DecodeError::missing(
|
||||
entry.get(),
|
||||
"keybind first defined here",
|
||||
));
|
||||
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"keybind",
|
||||
"duplicate keybind later defined here",
|
||||
));
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(child);
|
||||
binds.push(bind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -945,7 +1001,34 @@ impl FromStr for Key {
|
||||
} else if key.eq_ignore_ascii_case("TouchpadScrollRight") {
|
||||
Trigger::TouchpadScrollRight
|
||||
} else {
|
||||
let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
|
||||
let mut keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
|
||||
// The keyboard event handling code can receive either
|
||||
// XF86ScreenSaver or XF86Screensaver, because there is no
|
||||
// case mapping defined between these keysyms. If we just
|
||||
// use the case-insensitive version of keysym_from_name it
|
||||
// is not possible to bind the uppercase version, because the
|
||||
// case-insensitive match prefers the lowercase version when
|
||||
// there is a choice.
|
||||
//
|
||||
// Therefore, when we match this key with the initial
|
||||
// case-insensitive match we try a further case-sensitive match
|
||||
// (so that either key can be bound). If that fails, we change
|
||||
// to the uppercase version because:
|
||||
//
|
||||
// - A comment in xkb_keysym_from_name (in libxkbcommon) tells us that the uppercase
|
||||
// version is the "best" of the two. [0]
|
||||
// - The xkbcommon crate only has a constant for ScreenSaver. [1]
|
||||
//
|
||||
// [0]: https://github.com/xkbcommon/libxkbcommon/blob/45a118d5325b051343b4b174f60c1434196fa7d4/src/keysym.c#L276
|
||||
// [1]: https://docs.rs/xkbcommon/latest/xkbcommon/xkb/keysyms/index.html#:~:text=KEY%5FXF86ScreenSaver
|
||||
//
|
||||
// See https://github.com/niri-wm/niri/issues/1969
|
||||
if keysym == Keysym::XF86_Screensaver {
|
||||
keysym = keysym_from_name(key, KEYSYM_NO_FLAGS);
|
||||
if keysym.raw() == KEY_NoSymbol {
|
||||
keysym = Keysym::XF86_ScreenSaver;
|
||||
}
|
||||
}
|
||||
if keysym.raw() == KEY_NoSymbol {
|
||||
return Err(miette!("invalid key: {key}"));
|
||||
}
|
||||
@@ -960,6 +1043,31 @@ impl FromStr for Key {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_xf86_screensaver() {
|
||||
assert_eq!(
|
||||
"XF86ScreenSaver".parse::<Key>().unwrap(),
|
||||
Key {
|
||||
trigger: Trigger::Keysym(Keysym::XF86_ScreenSaver),
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
"XF86Screensaver".parse::<Key>().unwrap(),
|
||||
Key {
|
||||
trigger: Trigger::Keysym(Keysym::XF86_Screensaver),
|
||||
modifiers: Modifiers::empty(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
"xf86screensaver".parse::<Key>().unwrap(),
|
||||
Key {
|
||||
trigger: Trigger::Keysym(Keysym::XF86_ScreenSaver),
|
||||
modifiers: Modifiers::empty(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_iso_level_shifts() {
|
||||
assert_eq!(
|
||||
|
||||
+80
-18
@@ -1,45 +1,107 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
use crate::utils::{Flag, MergeWith};
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Debug {
|
||||
pub preview_render: Option<PreviewRender>,
|
||||
pub dbus_interfaces_in_non_session_instances: bool,
|
||||
pub wait_for_frame_completion_before_queueing: bool,
|
||||
pub enable_overlay_planes: bool,
|
||||
pub disable_cursor_plane: bool,
|
||||
pub disable_direct_scanout: bool,
|
||||
pub keep_max_bpc_unchanged: bool,
|
||||
pub restrict_primary_scanout_to_matching_format: bool,
|
||||
pub force_disable_connectors_on_resume: bool,
|
||||
pub render_drm_device: Option<PathBuf>,
|
||||
pub ignored_drm_devices: Vec<PathBuf>,
|
||||
pub force_pipewire_invalid_modifier: bool,
|
||||
pub emulate_zero_presentation_time: bool,
|
||||
pub disable_resize_throttling: bool,
|
||||
pub disable_transactions: bool,
|
||||
pub keep_laptop_panel_on_when_lid_is_closed: bool,
|
||||
pub disable_monitor_names: bool,
|
||||
pub strict_new_window_focus_policy: bool,
|
||||
pub honor_xdg_activation_with_invalid_serial: bool,
|
||||
pub deactivate_unfocused_windows: bool,
|
||||
pub skip_cursor_only_updates_during_vrr: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct DebugPart {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub preview_render: Option<PreviewRender>,
|
||||
#[knuffel(child)]
|
||||
pub dbus_interfaces_in_non_session_instances: bool,
|
||||
pub dbus_interfaces_in_non_session_instances: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub wait_for_frame_completion_before_queueing: bool,
|
||||
pub wait_for_frame_completion_before_queueing: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub enable_overlay_planes: bool,
|
||||
pub enable_overlay_planes: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_cursor_plane: bool,
|
||||
pub disable_cursor_plane: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_direct_scanout: bool,
|
||||
pub disable_direct_scanout: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub keep_max_bpc_unchanged: bool,
|
||||
pub keep_max_bpc_unchanged: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub restrict_primary_scanout_to_matching_format: bool,
|
||||
pub restrict_primary_scanout_to_matching_format: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub force_disable_connectors_on_resume: Option<Flag>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub render_drm_device: Option<PathBuf>,
|
||||
#[knuffel(children(name = "ignore-drm-device"), unwrap(argument))]
|
||||
pub ignored_drm_devices: Vec<PathBuf>,
|
||||
#[knuffel(child)]
|
||||
pub force_pipewire_invalid_modifier: bool,
|
||||
pub force_pipewire_invalid_modifier: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub emulate_zero_presentation_time: bool,
|
||||
pub emulate_zero_presentation_time: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_resize_throttling: bool,
|
||||
pub disable_resize_throttling: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_transactions: bool,
|
||||
pub disable_transactions: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub keep_laptop_panel_on_when_lid_is_closed: bool,
|
||||
pub keep_laptop_panel_on_when_lid_is_closed: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_monitor_names: bool,
|
||||
pub disable_monitor_names: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub strict_new_window_focus_policy: bool,
|
||||
pub strict_new_window_focus_policy: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub honor_xdg_activation_with_invalid_serial: bool,
|
||||
pub honor_xdg_activation_with_invalid_serial: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub deactivate_unfocused_windows: bool,
|
||||
pub deactivate_unfocused_windows: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub skip_cursor_only_updates_during_vrr: bool,
|
||||
pub skip_cursor_only_updates_during_vrr: Option<Flag>,
|
||||
}
|
||||
|
||||
impl MergeWith<DebugPart> for Debug {
|
||||
fn merge_with(&mut self, part: &DebugPart) {
|
||||
merge!(
|
||||
(self, part),
|
||||
dbus_interfaces_in_non_session_instances,
|
||||
wait_for_frame_completion_before_queueing,
|
||||
enable_overlay_planes,
|
||||
disable_cursor_plane,
|
||||
disable_direct_scanout,
|
||||
keep_max_bpc_unchanged,
|
||||
restrict_primary_scanout_to_matching_format,
|
||||
force_disable_connectors_on_resume,
|
||||
force_pipewire_invalid_modifier,
|
||||
emulate_zero_presentation_time,
|
||||
disable_resize_throttling,
|
||||
disable_transactions,
|
||||
keep_laptop_panel_on_when_lid_is_closed,
|
||||
disable_monitor_names,
|
||||
strict_new_window_focus_policy,
|
||||
honor_xdg_activation_with_invalid_serial,
|
||||
deactivate_unfocused_windows,
|
||||
skip_cursor_only_updates_during_vrr,
|
||||
);
|
||||
|
||||
merge_clone_opt!((self, part), preview_render, render_drm_device);
|
||||
|
||||
self.ignored_drm_devices
|
||||
.extend(part.ignored_drm_devices.iter().cloned());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use miette::Diagnostic;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigParseResult<T, E> {
|
||||
pub config: Result<T, E>,
|
||||
|
||||
// We always try to return includes for the file watcher.
|
||||
//
|
||||
// If the main config is valid, but an included file fails to parse, config will be an Err(),
|
||||
// but includes will still be filled, so that fixing just the included file is enough to
|
||||
// trigger a reload.
|
||||
pub includes: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
/// Error type that chains main errors with include errors.
|
||||
///
|
||||
/// Allows miette's Report formatting to have main + include errors all in one.
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigIncludeError {
|
||||
pub main: knuffel::Error,
|
||||
pub includes: Vec<knuffel::Error>,
|
||||
}
|
||||
|
||||
impl<T, E> ConfigParseResult<T, E> {
|
||||
pub fn from_err(err: E) -> Self {
|
||||
Self {
|
||||
config: Err(err),
|
||||
includes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_config_res<U, V>(
|
||||
self,
|
||||
f: impl FnOnce(Result<T, E>) -> Result<U, V>,
|
||||
) -> ConfigParseResult<U, V> {
|
||||
ConfigParseResult {
|
||||
config: f(self.config),
|
||||
includes: self.includes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigIncludeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.main, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConfigIncludeError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
self.main.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for ConfigIncludeError {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
self.main.code()
|
||||
}
|
||||
|
||||
fn severity(&self) -> Option<miette::Severity> {
|
||||
self.main.severity()
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
self.main.help()
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
self.main.url()
|
||||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||
self.main.source_code()
|
||||
}
|
||||
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
self.main.labels()
|
||||
}
|
||||
|
||||
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
|
||||
self.main.diagnostic_source()
|
||||
}
|
||||
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||
let main_related = self.main.related();
|
||||
let includes_iter = self.includes.iter().map(|err| err as &'a dyn Diagnostic);
|
||||
|
||||
let iter: Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a> = match main_related {
|
||||
Some(main) => Box::new(main.chain(includes_iter)),
|
||||
None => Box::new(includes_iter),
|
||||
};
|
||||
|
||||
Some(iter)
|
||||
}
|
||||
}
|
||||
+74
-19
@@ -1,57 +1,112 @@
|
||||
use crate::utils::MergeWith;
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Gestures {
|
||||
#[knuffel(child, default)]
|
||||
pub dnd_edge_view_scroll: DndEdgeViewScroll,
|
||||
#[knuffel(child, default)]
|
||||
pub dnd_edge_workspace_switch: DndEdgeWorkspaceSwitch,
|
||||
#[knuffel(child, default)]
|
||||
pub hot_corners: HotCorners,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct GesturesPart {
|
||||
#[knuffel(child)]
|
||||
pub dnd_edge_view_scroll: Option<DndEdgeViewScrollPart>,
|
||||
#[knuffel(child)]
|
||||
pub dnd_edge_workspace_switch: Option<DndEdgeWorkspaceSwitchPart>,
|
||||
#[knuffel(child)]
|
||||
pub hot_corners: Option<HotCorners>,
|
||||
}
|
||||
|
||||
impl MergeWith<GesturesPart> for Gestures {
|
||||
fn merge_with(&mut self, part: &GesturesPart) {
|
||||
merge!(
|
||||
(self, part),
|
||||
dnd_edge_view_scroll,
|
||||
dnd_edge_workspace_switch,
|
||||
);
|
||||
merge_clone!((self, part), hot_corners);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DndEdgeViewScroll {
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().trigger_width)]
|
||||
pub trigger_width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().delay_ms)]
|
||||
pub trigger_width: f64,
|
||||
pub delay_ms: u16,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().max_speed)]
|
||||
pub max_speed: FloatOrInt<0, 1_000_000>,
|
||||
pub max_speed: f64,
|
||||
}
|
||||
|
||||
impl Default for DndEdgeViewScroll {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
trigger_width: FloatOrInt(30.), // Taken from GTK 4.
|
||||
trigger_width: 30., // Taken from GTK 4.
|
||||
delay_ms: 100,
|
||||
max_speed: FloatOrInt(1500.),
|
||||
max_speed: 1500.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DndEdgeViewScrollPart {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub trigger_width: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub delay_ms: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_speed: Option<FloatOrInt<0, 1_000_000>>,
|
||||
}
|
||||
|
||||
impl MergeWith<DndEdgeViewScrollPart> for DndEdgeViewScroll {
|
||||
fn merge_with(&mut self, part: &DndEdgeViewScrollPart) {
|
||||
merge!((self, part), trigger_width, max_speed);
|
||||
merge_clone!((self, part), delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DndEdgeWorkspaceSwitch {
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().trigger_height)]
|
||||
pub trigger_height: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().delay_ms)]
|
||||
pub trigger_height: f64,
|
||||
pub delay_ms: u16,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().max_speed)]
|
||||
pub max_speed: FloatOrInt<0, 1_000_000>,
|
||||
pub max_speed: f64,
|
||||
}
|
||||
|
||||
impl Default for DndEdgeWorkspaceSwitch {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
trigger_height: FloatOrInt(50.),
|
||||
trigger_height: 50.,
|
||||
delay_ms: 100,
|
||||
max_speed: FloatOrInt(1500.),
|
||||
max_speed: 1500.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DndEdgeWorkspaceSwitchPart {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub trigger_height: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub delay_ms: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_speed: Option<FloatOrInt<0, 1_000_000>>,
|
||||
}
|
||||
|
||||
impl MergeWith<DndEdgeWorkspaceSwitchPart> for DndEdgeWorkspaceSwitch {
|
||||
fn merge_with(&mut self, part: &DndEdgeWorkspaceSwitchPart) {
|
||||
merge!((self, part), trigger_height, max_speed);
|
||||
merge_clone!((self, part), delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct HotCorners {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub top_left: bool,
|
||||
#[knuffel(child)]
|
||||
pub top_right: bool,
|
||||
#[knuffel(child)]
|
||||
pub bottom_left: bool,
|
||||
#[knuffel(child)]
|
||||
pub bottom_right: bool,
|
||||
}
|
||||
|
||||
+94
-27
@@ -5,51 +5,91 @@ use smithay::input::keyboard::XkbConfig;
|
||||
use smithay::reexports::input;
|
||||
|
||||
use crate::binds::Modifiers;
|
||||
use crate::utils::Percent;
|
||||
use crate::utils::{Flag, MergeWith, Percent};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Input {
|
||||
#[knuffel(child, default)]
|
||||
pub keyboard: Keyboard,
|
||||
#[knuffel(child, default)]
|
||||
pub touchpad: Touchpad,
|
||||
#[knuffel(child, default)]
|
||||
pub mouse: Mouse,
|
||||
#[knuffel(child, default)]
|
||||
pub trackpoint: Trackpoint,
|
||||
#[knuffel(child, default)]
|
||||
pub trackball: Trackball,
|
||||
#[knuffel(child, default)]
|
||||
pub tablet: Tablet,
|
||||
#[knuffel(child, default)]
|
||||
pub touch: Touch,
|
||||
#[knuffel(child)]
|
||||
pub disable_power_key_handling: bool,
|
||||
pub warp_mouse_to_focus: Option<WarpMouseToFocus>,
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouse>,
|
||||
pub workspace_auto_back_and_forth: bool,
|
||||
pub mod_key: Option<ModKey>,
|
||||
pub mod_key_nested: Option<ModKey>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct InputPart {
|
||||
#[knuffel(child)]
|
||||
pub keyboard: Option<KeyboardPart>,
|
||||
#[knuffel(child)]
|
||||
pub touchpad: Option<Touchpad>,
|
||||
#[knuffel(child)]
|
||||
pub mouse: Option<Mouse>,
|
||||
#[knuffel(child)]
|
||||
pub trackpoint: Option<Trackpoint>,
|
||||
#[knuffel(child)]
|
||||
pub trackball: Option<Trackball>,
|
||||
#[knuffel(child)]
|
||||
pub tablet: Option<Tablet>,
|
||||
#[knuffel(child)]
|
||||
pub touch: Option<Touch>,
|
||||
#[knuffel(child)]
|
||||
pub disable_power_key_handling: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub warp_mouse_to_focus: Option<WarpMouseToFocus>,
|
||||
#[knuffel(child)]
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouse>,
|
||||
#[knuffel(child)]
|
||||
pub workspace_auto_back_and_forth: bool,
|
||||
pub workspace_auto_back_and_forth: Option<Flag>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mod_key: Option<ModKey>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mod_key_nested: Option<ModKey>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq, Eq)]
|
||||
impl MergeWith<InputPart> for Input {
|
||||
fn merge_with(&mut self, part: &InputPart) {
|
||||
merge!(
|
||||
(self, part),
|
||||
keyboard,
|
||||
disable_power_key_handling,
|
||||
workspace_auto_back_and_forth,
|
||||
);
|
||||
|
||||
merge_clone!(
|
||||
(self, part),
|
||||
touchpad,
|
||||
mouse,
|
||||
trackpoint,
|
||||
trackball,
|
||||
tablet,
|
||||
touch,
|
||||
);
|
||||
|
||||
merge_clone_opt!(
|
||||
(self, part),
|
||||
warp_mouse_to_focus,
|
||||
focus_follows_mouse,
|
||||
mod_key,
|
||||
mod_key_nested,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Keyboard {
|
||||
#[knuffel(child, default)]
|
||||
pub xkb: Xkb,
|
||||
// The defaults were chosen to match wlroots and sway.
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().repeat_delay)]
|
||||
pub repeat_delay: u16,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().repeat_rate)]
|
||||
pub repeat_rate: u8,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub track_layout: TrackLayout,
|
||||
#[knuffel(child)]
|
||||
pub numlock: bool,
|
||||
}
|
||||
|
||||
@@ -57,6 +97,7 @@ impl Default for Keyboard {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
xkb: Default::default(),
|
||||
// The defaults were chosen to match wlroots and sway.
|
||||
repeat_delay: 600,
|
||||
repeat_rate: 25,
|
||||
track_layout: Default::default(),
|
||||
@@ -65,6 +106,27 @@ impl Default for Keyboard {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq, Eq)]
|
||||
pub struct KeyboardPart {
|
||||
#[knuffel(child)]
|
||||
pub xkb: Option<Xkb>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub repeat_delay: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub repeat_rate: Option<u8>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub track_layout: Option<TrackLayout>,
|
||||
#[knuffel(child)]
|
||||
pub numlock: Option<Flag>,
|
||||
}
|
||||
|
||||
impl MergeWith<KeyboardPart> for Keyboard {
|
||||
fn merge_with(&mut self, part: &KeyboardPart) {
|
||||
merge_clone!((self, part), xkb, repeat_delay, repeat_rate, track_layout);
|
||||
merge!((self, part), numlock);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub struct Xkb {
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
@@ -93,7 +155,7 @@ impl Xkb {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackLayout {
|
||||
/// The layout change is global.
|
||||
#[default]
|
||||
@@ -121,7 +183,7 @@ impl ScrollFactor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Touchpad {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
@@ -161,7 +223,7 @@ pub struct Touchpad {
|
||||
pub scroll_factor: Option<ScrollFactor>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Mouse {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
@@ -185,7 +247,7 @@ pub struct Mouse {
|
||||
pub scroll_factor: Option<ScrollFactor>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Trackpoint {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
@@ -207,7 +269,7 @@ pub struct Trackpoint {
|
||||
pub middle_emulation: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Trackball {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
@@ -293,7 +355,7 @@ impl From<TapButtonMap> for input::TapButtonMap {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Tablet {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
@@ -302,13 +364,17 @@ pub struct Tablet {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub map_to_output: Option<String>,
|
||||
#[knuffel(child)]
|
||||
pub map_to_focused_output: bool,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Touch {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(arguments))]
|
||||
pub calibration_matrix: Option<Vec<f32>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub map_to_output: Option<String>,
|
||||
}
|
||||
@@ -450,9 +516,10 @@ mod tests {
|
||||
|
||||
#[track_caller]
|
||||
fn do_parse(text: &str) -> Input {
|
||||
knuffel::parse("test.kdl", text)
|
||||
let part = knuffel::parse("test.kdl", text)
|
||||
.map_err(miette::Report::new)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
Input::from_part(&part)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::appearance::{BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use crate::appearance::{BackgroundEffectRule, BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use crate::utils::RegexEq;
|
||||
use crate::window_rule::PopupsRule;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct LayerRule {
|
||||
@@ -20,6 +21,10 @@ pub struct LayerRule {
|
||||
pub place_within_backdrop: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub baba_is_float: Option<bool>,
|
||||
#[knuffel(child, default)]
|
||||
pub background_effect: BackgroundEffectRule,
|
||||
#[knuffel(child, default)]
|
||||
pub popups: PopupsRule,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
@@ -28,4 +33,6 @@ pub struct Match {
|
||||
pub namespace: Option<RegexEq>,
|
||||
#[knuffel(property)]
|
||||
pub at_startup: Option<bool>,
|
||||
#[knuffel(property, str)]
|
||||
pub layer: Option<niri_ipc::Layer>,
|
||||
}
|
||||
|
||||
+96
-31
@@ -4,65 +4,130 @@ use niri_ipc::{ColumnDisplay, SizeChange};
|
||||
use crate::appearance::{
|
||||
Border, FocusRing, InsertHint, Shadow, TabIndicator, DEFAULT_BACKGROUND_COLOR,
|
||||
};
|
||||
use crate::utils::expect_only_children;
|
||||
use crate::{Color, FloatOrInt};
|
||||
use crate::utils::{expect_only_children, Flag, MergeWith};
|
||||
use crate::{BorderRule, Color, FloatOrInt, InsertHintPart, ShadowRule, TabIndicatorPart};
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Layout {
|
||||
#[knuffel(child, default)]
|
||||
pub focus_ring: FocusRing,
|
||||
#[knuffel(child, default)]
|
||||
pub border: Border,
|
||||
#[knuffel(child, default)]
|
||||
pub shadow: Shadow,
|
||||
#[knuffel(child, default)]
|
||||
pub tab_indicator: TabIndicator,
|
||||
#[knuffel(child, default)]
|
||||
pub insert_hint: InsertHint,
|
||||
#[knuffel(child, unwrap(children), default)]
|
||||
pub preset_column_widths: Vec<PresetSize>,
|
||||
#[knuffel(child)]
|
||||
pub default_column_width: Option<DefaultPresetSize>,
|
||||
#[knuffel(child, unwrap(children), default)]
|
||||
pub default_column_width: Option<PresetSize>,
|
||||
pub preset_window_heights: Vec<PresetSize>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub center_focused_column: CenterFocusedColumn,
|
||||
#[knuffel(child)]
|
||||
pub always_center_single_column: bool,
|
||||
#[knuffel(child)]
|
||||
pub empty_workspace_above_first: bool,
|
||||
#[knuffel(child, unwrap(argument, str), default = Self::default().default_column_display)]
|
||||
pub default_column_display: ColumnDisplay,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().gaps)]
|
||||
pub gaps: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default)]
|
||||
pub gaps: f64,
|
||||
pub struts: Struts,
|
||||
#[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)]
|
||||
pub background_color: Color,
|
||||
}
|
||||
|
||||
impl Default for Layout {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
focus_ring: Default::default(),
|
||||
border: Default::default(),
|
||||
shadow: Default::default(),
|
||||
tab_indicator: Default::default(),
|
||||
insert_hint: Default::default(),
|
||||
preset_column_widths: Default::default(),
|
||||
default_column_width: Default::default(),
|
||||
center_focused_column: Default::default(),
|
||||
focus_ring: FocusRing::default(),
|
||||
border: Border::default(),
|
||||
shadow: Shadow::default(),
|
||||
tab_indicator: TabIndicator::default(),
|
||||
insert_hint: InsertHint::default(),
|
||||
preset_column_widths: vec![
|
||||
PresetSize::Proportion(1. / 3.),
|
||||
PresetSize::Proportion(0.5),
|
||||
PresetSize::Proportion(2. / 3.),
|
||||
],
|
||||
default_column_width: Some(PresetSize::Proportion(0.5)),
|
||||
center_focused_column: CenterFocusedColumn::Never,
|
||||
always_center_single_column: false,
|
||||
empty_workspace_above_first: false,
|
||||
default_column_display: ColumnDisplay::Normal,
|
||||
gaps: FloatOrInt(16.),
|
||||
struts: Default::default(),
|
||||
preset_window_heights: Default::default(),
|
||||
gaps: 16.,
|
||||
struts: Struts::default(),
|
||||
preset_window_heights: vec![
|
||||
PresetSize::Proportion(1. / 3.),
|
||||
PresetSize::Proportion(0.5),
|
||||
PresetSize::Proportion(2. / 3.),
|
||||
],
|
||||
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<LayoutPart> for Layout {
|
||||
fn merge_with(&mut self, part: &LayoutPart) {
|
||||
merge!(
|
||||
(self, part),
|
||||
focus_ring,
|
||||
border,
|
||||
shadow,
|
||||
tab_indicator,
|
||||
insert_hint,
|
||||
always_center_single_column,
|
||||
empty_workspace_above_first,
|
||||
gaps,
|
||||
);
|
||||
|
||||
merge_clone!(
|
||||
(self, part),
|
||||
preset_column_widths,
|
||||
preset_window_heights,
|
||||
center_focused_column,
|
||||
default_column_display,
|
||||
struts,
|
||||
background_color,
|
||||
);
|
||||
|
||||
if let Some(x) = part.default_column_width {
|
||||
self.default_column_width = x.0;
|
||||
}
|
||||
|
||||
if self.preset_column_widths.is_empty() {
|
||||
self.preset_column_widths = Layout::default().preset_column_widths;
|
||||
}
|
||||
|
||||
if self.preset_window_heights.is_empty() {
|
||||
self.preset_window_heights = Layout::default().preset_window_heights;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct LayoutPart {
|
||||
#[knuffel(child)]
|
||||
pub focus_ring: Option<BorderRule>,
|
||||
#[knuffel(child)]
|
||||
pub border: Option<BorderRule>,
|
||||
#[knuffel(child)]
|
||||
pub shadow: Option<ShadowRule>,
|
||||
#[knuffel(child)]
|
||||
pub tab_indicator: Option<TabIndicatorPart>,
|
||||
#[knuffel(child)]
|
||||
pub insert_hint: Option<InsertHintPart>,
|
||||
#[knuffel(child, unwrap(children))]
|
||||
pub preset_column_widths: Option<Vec<PresetSize>>,
|
||||
#[knuffel(child)]
|
||||
pub default_column_width: Option<DefaultPresetSize>,
|
||||
#[knuffel(child, unwrap(children))]
|
||||
pub preset_window_heights: Option<Vec<PresetSize>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub center_focused_column: Option<CenterFocusedColumn>,
|
||||
#[knuffel(child)]
|
||||
pub always_center_single_column: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub empty_workspace_above_first: Option<Flag>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub default_column_display: Option<ColumnDisplay>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub gaps: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child)]
|
||||
pub struts: Option<Struts>,
|
||||
#[knuffel(child)]
|
||||
pub background_color: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PresetSize {
|
||||
Proportion(#[knuffel(argument)] f64),
|
||||
|
||||
+846
-134
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
macro_rules! merge {
|
||||
(($self:expr, $part:expr), $($field:ident),+ $(,)*) => {
|
||||
$(
|
||||
if let Some(x) = &$part.$field {
|
||||
$self.$field.merge_with(x);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! merge_clone {
|
||||
(($self:expr, $part:expr), $($field:ident),+ $(,)*) => {
|
||||
$(
|
||||
if let Some(x) = &$part.$field {
|
||||
$self.$field.clone_from(x);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! merge_clone_opt {
|
||||
(($self:expr, $part:expr), $($field:ident),+ $(,)*) => {
|
||||
$(
|
||||
if $part.$field.is_some() {
|
||||
$self.$field.clone_from(&$part.$field);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! merge_color_gradient {
|
||||
(($self:expr, $part:expr), $(($color:ident, $gradient:ident)),+ $(,)*) => {
|
||||
$(
|
||||
if let Some(x) = $part.$color {
|
||||
$self.$color = x;
|
||||
$self.$gradient = None;
|
||||
}
|
||||
if let Some(x) = $part.$gradient {
|
||||
$self.$gradient = Some(x);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! merge_color_gradient_opt {
|
||||
(($self:expr, $part:expr), $(($color:ident, $gradient:ident)),+ $(,)*) => {
|
||||
$(
|
||||
if let Some(x) = $part.$color {
|
||||
$self.$color = Some(x);
|
||||
$self.$gradient = None;
|
||||
}
|
||||
if let Some(x) = $part.$gradient {
|
||||
$self.$gradient = Some(x);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! merge_on_off {
|
||||
(($self:expr, $part:expr)) => {
|
||||
if $part.off {
|
||||
$self.off = true;
|
||||
$self.on = false;
|
||||
}
|
||||
|
||||
if $part.on {
|
||||
$self.off = false;
|
||||
$self.on = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
+115
-20
@@ -1,4 +1,5 @@
|
||||
use crate::appearance::{Color, WorkspaceShadow, DEFAULT_BACKDROP_COLOR};
|
||||
use crate::appearance::{Color, WorkspaceShadow, WorkspaceShadowPart, DEFAULT_BACKDROP_COLOR};
|
||||
use crate::utils::{Flag, MergeWith};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
@@ -13,15 +14,11 @@ pub struct SpawnShAtStartup {
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Cursor {
|
||||
#[knuffel(child, unwrap(argument), default = String::from("default"))]
|
||||
pub xcursor_theme: String,
|
||||
#[knuffel(child, unwrap(argument), default = 24)]
|
||||
pub xcursor_size: u8,
|
||||
#[knuffel(child)]
|
||||
pub hide_when_typing: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub hide_after_inactive_ms: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -36,46 +33,125 @@ impl Default for Cursor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(knuffel::Decode, Debug, PartialEq)]
|
||||
pub struct CursorPart {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub xcursor_theme: Option<String>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub xcursor_size: Option<u8>,
|
||||
#[knuffel(child)]
|
||||
pub hide_when_typing: Option<Flag>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub hide_after_inactive_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl MergeWith<CursorPart> for Cursor {
|
||||
fn merge_with(&mut self, part: &CursorPart) {
|
||||
merge_clone!((self, part), xcursor_theme, xcursor_size);
|
||||
merge!((self, part), hide_when_typing);
|
||||
merge_clone_opt!((self, part), hide_after_inactive_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct ScreenshotPath(#[knuffel(argument)] pub Option<String>);
|
||||
|
||||
impl Default for ScreenshotPath {
|
||||
fn default() -> Self {
|
||||
Self(Some(String::from(
|
||||
"~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct HotkeyOverlay {
|
||||
#[knuffel(child)]
|
||||
pub skip_at_startup: bool,
|
||||
#[knuffel(child)]
|
||||
pub hide_not_bound: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ConfigNotification {
|
||||
pub struct HotkeyOverlayPart {
|
||||
#[knuffel(child)]
|
||||
pub skip_at_startup: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub hide_not_bound: Option<Flag>,
|
||||
}
|
||||
|
||||
impl MergeWith<HotkeyOverlayPart> for HotkeyOverlay {
|
||||
fn merge_with(&mut self, part: &HotkeyOverlayPart) {
|
||||
merge!((self, part), skip_at_startup, hide_not_bound);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ConfigNotification {
|
||||
pub disable_failed: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Clipboard {
|
||||
pub struct ConfigNotificationPart {
|
||||
#[knuffel(child)]
|
||||
pub disable_failed: Option<Flag>,
|
||||
}
|
||||
|
||||
impl MergeWith<ConfigNotificationPart> for ConfigNotification {
|
||||
fn merge_with(&mut self, part: &ConfigNotificationPart) {
|
||||
merge!((self, part), disable_failed);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Clipboard {
|
||||
pub disable_primary: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ClipboardPart {
|
||||
#[knuffel(child)]
|
||||
pub disable_primary: Option<Flag>,
|
||||
}
|
||||
|
||||
impl MergeWith<ClipboardPart> for Clipboard {
|
||||
fn merge_with(&mut self, part: &ClipboardPart) {
|
||||
merge!((self, part), disable_primary);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Overview {
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().zoom)]
|
||||
pub zoom: FloatOrInt<0, 1>,
|
||||
#[knuffel(child, default = Self::default().backdrop_color)]
|
||||
pub zoom: f64,
|
||||
pub backdrop_color: Color,
|
||||
#[knuffel(child, default)]
|
||||
pub workspace_shadow: WorkspaceShadow,
|
||||
}
|
||||
|
||||
impl Default for Overview {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
zoom: FloatOrInt(0.5),
|
||||
zoom: 0.5,
|
||||
backdrop_color: DEFAULT_BACKDROP_COLOR,
|
||||
workspace_shadow: WorkspaceShadow::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct OverviewPart {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub zoom: Option<FloatOrInt<0, 1>>,
|
||||
#[knuffel(child)]
|
||||
pub backdrop_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub workspace_shadow: Option<WorkspaceShadowPart>,
|
||||
}
|
||||
|
||||
impl MergeWith<OverviewPart> for Overview {
|
||||
fn merge_with(&mut self, part: &OverviewPart) {
|
||||
merge!((self, part), zoom, workspace_shadow);
|
||||
merge_clone!((self, part), backdrop_color);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct Environment(#[knuffel(children)] pub Vec<EnvironmentVariable>);
|
||||
|
||||
@@ -87,11 +163,9 @@ pub struct EnvironmentVariable {
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct XwaylandSatellite {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().path)]
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
@@ -103,3 +177,24 @@ impl Default for XwaylandSatellite {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct XwaylandSatellitePart {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
impl MergeWith<XwaylandSatellitePart> for XwaylandSatellite {
|
||||
fn merge_with(&mut self, part: &XwaylandSatellitePart) {
|
||||
self.off |= part.off;
|
||||
if part.on {
|
||||
self.off = false;
|
||||
}
|
||||
|
||||
merge_clone!((self, part), path);
|
||||
}
|
||||
}
|
||||
|
||||
+292
-4
@@ -1,10 +1,52 @@
|
||||
use niri_ipc::{ConfiguredMode, Transform};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{Color, FloatOrInt};
|
||||
use knuffel::ast::SpannedNode;
|
||||
use knuffel::decode::Context;
|
||||
use knuffel::errors::DecodeError;
|
||||
use knuffel::traits::ErrorSpan;
|
||||
use knuffel::Decode;
|
||||
use niri_ipc::{ConfiguredMode, HSyncPolarity, Transform, VSyncPolarity};
|
||||
|
||||
use crate::gestures::HotCorners;
|
||||
use crate::{Color, FloatOrInt, LayoutPart};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Outputs(pub Vec<Output>);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Mode {
|
||||
pub custom: bool,
|
||||
pub mode: ConfiguredMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Modeline {
|
||||
/// The rate at which pixels are drawn in MHz.
|
||||
pub clock: f64,
|
||||
/// Horizontal active pixels.
|
||||
pub hdisplay: u16,
|
||||
/// Horizontal sync pulse start position in pixels.
|
||||
pub hsync_start: u16,
|
||||
/// Horizontal sync pulse end position in pixels.
|
||||
pub hsync_end: u16,
|
||||
/// Total horizontal number of pixels before resetting the horizontal drawing position to
|
||||
/// zero.
|
||||
pub htotal: u16,
|
||||
|
||||
/// Vertical active pixels.
|
||||
pub vdisplay: u16,
|
||||
/// Vertical sync pulse start position in pixels.
|
||||
pub vsync_start: u16,
|
||||
/// Vertical sync pulse end position in pixels.
|
||||
pub vsync_end: u16,
|
||||
/// Total vertical number of pixels before resetting the vertical drawing position to zero.
|
||||
pub vtotal: u16,
|
||||
/// Horizontal sync polarity: "+hsync" or "-hsync".
|
||||
pub hsync_polarity: niri_ipc::HSyncPolarity,
|
||||
/// Vertical sync polarity: "+vsync" or "-vsync".
|
||||
pub vsync_polarity: niri_ipc::VSyncPolarity,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Output {
|
||||
#[knuffel(child)]
|
||||
@@ -17,16 +59,23 @@ pub struct Output {
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
pub position: Option<Position>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mode: Option<ConfiguredMode>,
|
||||
#[knuffel(child)]
|
||||
pub mode: Option<Mode>,
|
||||
#[knuffel(child)]
|
||||
pub modeline: Option<Modeline>,
|
||||
#[knuffel(child)]
|
||||
pub variable_refresh_rate: Option<Vrr>,
|
||||
#[knuffel(child)]
|
||||
pub focus_at_startup: bool,
|
||||
// Deprecated; use layout.background_color.
|
||||
#[knuffel(child)]
|
||||
pub background_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub backdrop_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub hot_corners: Option<HotCorners>,
|
||||
#[knuffel(child)]
|
||||
pub layout: Option<LayoutPart>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
@@ -53,9 +102,12 @@ impl Default for Output {
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
mode: None,
|
||||
modeline: None,
|
||||
variable_refresh_rate: None,
|
||||
background_color: None,
|
||||
backdrop_color: None,
|
||||
hot_corners: None,
|
||||
layout: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,6 +257,242 @@ impl OutputName {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ErrorSpan> knuffel::Decode<S> for Mode {
|
||||
fn decode_node(node: &SpannedNode<S>, ctx: &mut Context<S>) -> Result<Self, DecodeError<S>> {
|
||||
if let Some(type_name) = &node.type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut custom: Option<bool> = None;
|
||||
for (name, val) in &node.properties {
|
||||
match &***name {
|
||||
"custom" => {
|
||||
if custom.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
"unexpected duplicate property `custom`",
|
||||
))
|
||||
}
|
||||
custom = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?)
|
||||
}
|
||||
name_str => ctx.emit_error(DecodeError::unexpected(
|
||||
node,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name_str.escape_default()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
let custom = custom.unwrap_or(false);
|
||||
|
||||
let mut arguments = node.arguments.iter();
|
||||
let mode = if let Some(mode_str) = arguments.next() {
|
||||
let temp_mode: String = knuffel::traits::DecodeScalar::decode(mode_str, ctx)?;
|
||||
|
||||
let res = ConfiguredMode::from_str(temp_mode.as_str()).and_then(|mode| {
|
||||
if custom {
|
||||
if mode.refresh.is_none() {
|
||||
return Err("no refresh rate found; required for custom mode");
|
||||
} else if let Some(refresh) = mode.refresh {
|
||||
if refresh <= 0. {
|
||||
return Err("custom mode refresh rate must be > 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(mode)
|
||||
});
|
||||
res.map_err(|err_msg| DecodeError::conversion(&mode_str.literal, err_msg))?
|
||||
} else {
|
||||
return Err(DecodeError::missing(node, "argument `mode` is required"));
|
||||
};
|
||||
|
||||
if let Some(surplus) = arguments.next() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&surplus.literal,
|
||||
"argument",
|
||||
"unexpected argument",
|
||||
))
|
||||
}
|
||||
|
||||
Ok(Mode { custom, mode })
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $ctx:expr, $span:expr, $fmt:literal $($arg:tt)* ) => {
|
||||
if !$cond {
|
||||
$ctx.emit_error(DecodeError::Conversion {
|
||||
source: format!($fmt $($arg)*).into(),
|
||||
span: $span.literal.span().clone()
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<S: ErrorSpan> Decode<S> for Modeline {
|
||||
fn decode_node(node: &SpannedNode<S>, ctx: &mut Context<S>) -> Result<Self, DecodeError<S>> {
|
||||
if let Some(type_name) = &node.type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
for span in node.properties.keys() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
span,
|
||||
"node",
|
||||
format!("unexpected node `{}`", span.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut arguments = node.arguments.iter();
|
||||
|
||||
macro_rules! m_required {
|
||||
// This could be one identifier if macro_metavar_expr_concat stabilizes
|
||||
($field:ident, $value_field:ident) => {
|
||||
let $value_field = arguments.next().ok_or_else(|| {
|
||||
DecodeError::missing(node, format!("missing {} argument", stringify!($value)))
|
||||
})?;
|
||||
let $field = knuffel::traits::DecodeScalar::decode($value_field, ctx)?;
|
||||
};
|
||||
}
|
||||
|
||||
m_required!(clock, clock_value);
|
||||
m_required!(hdisplay, hdisplay_value);
|
||||
m_required!(hsync_start, hsync_start_value);
|
||||
m_required!(hsync_end, hsync_end_value);
|
||||
m_required!(htotal, htotal_value);
|
||||
m_required!(vdisplay, vdisplay_value);
|
||||
m_required!(vsync_start, vsync_start_value);
|
||||
m_required!(vsync_end, vsync_end_value);
|
||||
m_required!(vtotal, vtotal_value);
|
||||
m_required!(hsync_polarity, hsync_polarity_value);
|
||||
let hsync_polarity =
|
||||
HSyncPolarity::from_str(String::as_str(&hsync_polarity)).map_err(|msg| {
|
||||
DecodeError::Conversion {
|
||||
span: hsync_polarity_value.literal.span().clone(),
|
||||
source: msg.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
m_required!(vsync_polarity, vsync_polarity_value);
|
||||
let vsync_polarity =
|
||||
VSyncPolarity::from_str(String::as_str(&vsync_polarity)).map_err(|msg| {
|
||||
DecodeError::Conversion {
|
||||
span: vsync_polarity_value.literal.span().clone(),
|
||||
source: msg.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
ensure!(
|
||||
hdisplay < hsync_start,
|
||||
ctx,
|
||||
hdisplay_value,
|
||||
"hdisplay {} must be < hsync_start {}",
|
||||
hdisplay,
|
||||
hsync_start
|
||||
);
|
||||
ensure!(
|
||||
hsync_start < hsync_end,
|
||||
ctx,
|
||||
hsync_start_value,
|
||||
"hsync_start {} must be < hsync_end {}",
|
||||
hsync_start,
|
||||
hsync_end,
|
||||
);
|
||||
ensure!(
|
||||
hsync_end < htotal,
|
||||
ctx,
|
||||
hsync_end_value,
|
||||
"hsync_end {} must be < htotal {}",
|
||||
hsync_end,
|
||||
htotal,
|
||||
);
|
||||
ensure!(
|
||||
0u16 < htotal,
|
||||
ctx,
|
||||
htotal_value,
|
||||
"htotal {} must be > 0",
|
||||
htotal
|
||||
);
|
||||
ensure!(
|
||||
vdisplay < vsync_start,
|
||||
ctx,
|
||||
vdisplay_value,
|
||||
"vdisplay {} must be < vsync_start {}",
|
||||
vdisplay,
|
||||
vsync_start,
|
||||
);
|
||||
ensure!(
|
||||
vsync_start < vsync_end,
|
||||
ctx,
|
||||
vsync_start_value,
|
||||
"vsync_start {} must be < vsync_end {}",
|
||||
vsync_start,
|
||||
vsync_end,
|
||||
);
|
||||
ensure!(
|
||||
vsync_end < vtotal,
|
||||
ctx,
|
||||
vsync_end_value,
|
||||
"vsync_end {} must be < vtotal {}",
|
||||
vsync_end,
|
||||
vtotal,
|
||||
);
|
||||
ensure!(
|
||||
0u16 < vtotal,
|
||||
ctx,
|
||||
vtotal_value,
|
||||
"vtotal {} must be > 0",
|
||||
vtotal
|
||||
);
|
||||
|
||||
if let Some(extra) = arguments.next() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&extra.literal,
|
||||
"argument",
|
||||
"unexpected argument, all possible arguments were already provided",
|
||||
))
|
||||
}
|
||||
|
||||
Ok(Modeline {
|
||||
clock,
|
||||
hdisplay,
|
||||
hsync_start,
|
||||
hsync_end,
|
||||
htotal,
|
||||
vdisplay,
|
||||
vsync_start,
|
||||
vsync_end,
|
||||
vtotal,
|
||||
hsync_polarity,
|
||||
vsync_polarity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use knuffel::errors::DecodeError;
|
||||
use smithay::input::keyboard::Keysym;
|
||||
|
||||
use crate::utils::{expect_only_children, MergeWith};
|
||||
use crate::{Action, Bind, Color, FloatOrInt, Key, Modifiers, Trigger};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RecentWindows {
|
||||
pub on: bool,
|
||||
pub debounce_ms: u16,
|
||||
pub open_delay_ms: u16,
|
||||
pub highlight: MruHighlight,
|
||||
pub previews: MruPreviews,
|
||||
pub binds: Vec<Bind>,
|
||||
}
|
||||
|
||||
impl Default for RecentWindows {
|
||||
fn default() -> Self {
|
||||
RecentWindows {
|
||||
on: true,
|
||||
debounce_ms: 750,
|
||||
open_delay_ms: 150,
|
||||
highlight: MruHighlight::default(),
|
||||
previews: MruPreviews::default(),
|
||||
binds: default_binds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct RecentWindowsPart {
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub debounce_ms: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_delay_ms: Option<u16>,
|
||||
#[knuffel(child)]
|
||||
pub highlight: Option<MruHighlightPart>,
|
||||
#[knuffel(child)]
|
||||
pub previews: Option<MruPreviewsPart>,
|
||||
#[knuffel(child)]
|
||||
pub binds: Option<MruBinds>,
|
||||
}
|
||||
|
||||
impl MergeWith<RecentWindowsPart> for RecentWindows {
|
||||
fn merge_with(&mut self, part: &RecentWindowsPart) {
|
||||
self.on |= part.on;
|
||||
if part.off {
|
||||
self.on = false;
|
||||
}
|
||||
|
||||
merge_clone!((self, part), debounce_ms, open_delay_ms);
|
||||
merge!((self, part), highlight, previews);
|
||||
|
||||
if let Some(part) = &part.binds {
|
||||
// Remove existing binds matching any new bind.
|
||||
self.binds
|
||||
.retain(|bind| !part.0.iter().any(|new| new.key == bind.key));
|
||||
// Add all new binds.
|
||||
self.binds.extend(part.0.iter().cloned().map(Bind::from));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct MruHighlight {
|
||||
pub active_color: Color,
|
||||
pub urgent_color: Color,
|
||||
pub padding: f64,
|
||||
pub corner_radius: f64,
|
||||
}
|
||||
|
||||
impl Default for MruHighlight {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active_color: Color::new_unpremul(0.6, 0.6, 0.6, 1.),
|
||||
urgent_color: Color::new_unpremul(1., 0.6, 0.6, 1.),
|
||||
padding: 30.,
|
||||
corner_radius: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct MruHighlightPart {
|
||||
#[knuffel(child)]
|
||||
pub active_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub urgent_color: Option<Color>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub padding: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub corner_radius: Option<FloatOrInt<0, 65535>>,
|
||||
}
|
||||
|
||||
impl MergeWith<MruHighlightPart> for MruHighlight {
|
||||
fn merge_with(&mut self, part: &MruHighlightPart) {
|
||||
merge_clone!((self, part), active_color, urgent_color);
|
||||
merge!((self, part), padding, corner_radius);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct MruPreviews {
|
||||
pub max_height: f64,
|
||||
pub max_scale: f64,
|
||||
}
|
||||
|
||||
impl Default for MruPreviews {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_height: 480.,
|
||||
max_scale: 0.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct MruPreviewsPart {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_height: Option<FloatOrInt<1, 65535>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_scale: Option<FloatOrInt<0, 1>>,
|
||||
}
|
||||
|
||||
impl MergeWith<MruPreviewsPart> for MruPreviews {
|
||||
fn merge_with(&mut self, part: &MruPreviewsPart) {
|
||||
merge!((self, part), max_height, max_scale);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MruBind {
|
||||
// MRU bind keys must have a modifier, this is enforced during parsing. The switcher will close
|
||||
// once all modifiers are released.
|
||||
pub key: Key,
|
||||
pub action: MruAction,
|
||||
pub allow_inhibiting: bool,
|
||||
pub hotkey_overlay_title: Option<Option<String>>,
|
||||
}
|
||||
|
||||
impl From<MruBind> for Bind {
|
||||
fn from(x: MruBind) -> Self {
|
||||
Self {
|
||||
key: x.key,
|
||||
action: Action::from(x.action),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
allow_inhibiting: x.allow_inhibiting,
|
||||
hotkey_overlay_title: x.hotkey_overlay_title,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub enum MruDirection {
|
||||
/// Most recently used to least.
|
||||
#[default]
|
||||
Forward,
|
||||
/// Least recently used to most.
|
||||
Backward,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub enum MruScope {
|
||||
/// All windows.
|
||||
#[default]
|
||||
All,
|
||||
/// Windows on the active output.
|
||||
Output,
|
||||
/// Windows on the active workspace.
|
||||
Workspace,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub enum MruFilter {
|
||||
/// All windows.
|
||||
#[default]
|
||||
#[knuffel(skip)]
|
||||
All,
|
||||
/// Windows with the same app id as the active window.
|
||||
AppId,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub enum MruAction {
|
||||
NextWindow(
|
||||
#[knuffel(property(name = "scope"))] Option<MruScope>,
|
||||
#[knuffel(property(name = "filter"), default)] MruFilter,
|
||||
),
|
||||
PreviousWindow(
|
||||
#[knuffel(property(name = "scope"))] Option<MruScope>,
|
||||
#[knuffel(property(name = "filter"), default)] MruFilter,
|
||||
),
|
||||
}
|
||||
|
||||
impl From<MruAction> for Action {
|
||||
fn from(x: MruAction) -> Self {
|
||||
match x {
|
||||
MruAction::NextWindow(scope, filter) => Self::MruAdvance {
|
||||
direction: MruDirection::Forward,
|
||||
scope,
|
||||
filter: Some(filter),
|
||||
},
|
||||
MruAction::PreviousWindow(scope, filter) => Self::MruAdvance {
|
||||
direction: MruDirection::Backward,
|
||||
scope,
|
||||
filter: Some(filter),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct MruBinds(pub Vec<MruBind>);
|
||||
|
||||
fn default_binds() -> Vec<Bind> {
|
||||
let mut rv = Vec::new();
|
||||
|
||||
let mut push = |trigger, base_mod, filter| {
|
||||
rv.push(Bind::from(MruBind {
|
||||
key: Key {
|
||||
trigger: Trigger::Keysym(trigger),
|
||||
modifiers: base_mod,
|
||||
},
|
||||
action: MruAction::NextWindow(None, filter),
|
||||
allow_inhibiting: true,
|
||||
hotkey_overlay_title: None,
|
||||
}));
|
||||
rv.push(Bind::from(MruBind {
|
||||
key: Key {
|
||||
trigger: Trigger::Keysym(trigger),
|
||||
modifiers: base_mod | Modifiers::SHIFT,
|
||||
},
|
||||
action: MruAction::PreviousWindow(None, filter),
|
||||
allow_inhibiting: true,
|
||||
hotkey_overlay_title: None,
|
||||
}));
|
||||
};
|
||||
|
||||
for base_mod in [Modifiers::ALT, Modifiers::COMPOSITOR] {
|
||||
push(Keysym::Tab, base_mod, MruFilter::All);
|
||||
push(Keysym::grave, base_mod, MruFilter::AppId);
|
||||
}
|
||||
|
||||
rv
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for MruBinds
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
expect_only_children(node, ctx);
|
||||
|
||||
let mut seen_keys = HashSet::new();
|
||||
|
||||
let mut binds = Vec::new();
|
||||
|
||||
for child in node.children() {
|
||||
match MruBind::decode_node(child, ctx) {
|
||||
Ok(bind) => {
|
||||
if !seen_keys.insert(bind.key) {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"keybind",
|
||||
"duplicate keybind",
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
binds.push(bind);
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.emit_error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(binds))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for MruBind
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
if let Some(type_name) = &node.type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
|
||||
for val in node.arguments.iter() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
"no arguments expected for this node",
|
||||
));
|
||||
}
|
||||
|
||||
let key = node
|
||||
.node_name
|
||||
.parse::<Key>()
|
||||
.map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?;
|
||||
|
||||
// A modifier is required because MRU remains on screen as long as any modifier is held.
|
||||
if key.modifiers.is_empty() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&node.node_name,
|
||||
"keybind",
|
||||
"keybind must have a modifier key",
|
||||
));
|
||||
}
|
||||
|
||||
// FIXME: To support this, all the mods_with_mouse_binds()/mods_with_wheel_binds()/etc.
|
||||
// will need to learn about recent-windows bindings.
|
||||
if !matches!(key.trigger, Trigger::Keysym(_)) {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&node.node_name,
|
||||
"key",
|
||||
"key must be a keyboard key (others are unsupported here for now)",
|
||||
));
|
||||
}
|
||||
|
||||
let mut allow_inhibiting = true;
|
||||
let mut hotkey_overlay_title = None;
|
||||
for (name, val) in &node.properties {
|
||||
match &***name {
|
||||
"allow-inhibiting" => {
|
||||
allow_inhibiting = knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
}
|
||||
"hotkey-overlay-title" => {
|
||||
hotkey_overlay_title = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
name_str => {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut children = node.children();
|
||||
|
||||
// If the action is invalid but the key is fine, we still want to return something.
|
||||
// That way, the parent can handle the existence of duplicate keybinds,
|
||||
// even if their contents are not valid.
|
||||
let dummy = Self {
|
||||
key,
|
||||
action: MruAction::NextWindow(None, MruFilter::All),
|
||||
allow_inhibiting: true,
|
||||
hotkey_overlay_title: None,
|
||||
};
|
||||
|
||||
if let Some(child) = children.next() {
|
||||
for unwanted_child in children {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
unwanted_child,
|
||||
"node",
|
||||
"only one action is allowed per keybind",
|
||||
));
|
||||
}
|
||||
match MruAction::decode_node(child, ctx) {
|
||||
Ok(action) => Ok(Self {
|
||||
key,
|
||||
action,
|
||||
allow_inhibiting,
|
||||
hotkey_overlay_title,
|
||||
}),
|
||||
Err(e) => {
|
||||
ctx.emit_error(e);
|
||||
Ok(dummy)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.emit_error(DecodeError::missing(
|
||||
node,
|
||||
"expected an action for this keybind",
|
||||
));
|
||||
Ok(dummy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ use knuffel::errors::DecodeError;
|
||||
use miette::miette;
|
||||
use regex::Regex;
|
||||
|
||||
mod merge_with;
|
||||
pub use merge_with::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Percent(pub f64);
|
||||
|
||||
@@ -11,6 +14,15 @@ pub struct Percent(pub f64);
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
|
||||
|
||||
/// Flag, with an optional explicit value.
|
||||
///
|
||||
/// Intended to be used as an `Option<MaybeBool>` field, as a tri-state:
|
||||
/// - (missing): unset, `None`
|
||||
/// - just `field`: set, `Some(true)`
|
||||
/// - explicitly `field true` or `field false`: set, `Some(true)` or `Some(false)`
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Flag(#[knuffel(argument, default = true)] pub bool);
|
||||
|
||||
/// `Regex` that implements `PartialEq` by its string form.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RegexEq(pub Regex);
|
||||
@@ -48,6 +60,18 @@ impl FromStr for Percent {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MIN: i32, const MAX: i32> MergeWith<FloatOrInt<MIN, MAX>> for f64 {
|
||||
fn merge_with(&mut self, part: &FloatOrInt<MIN, MAX>) {
|
||||
*self = part.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<Flag> for bool {
|
||||
fn merge_with(&mut self, part: &Flag) {
|
||||
*self = part.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: knuffel::traits::ErrorSpan, const MIN: i32, const MAX: i32> knuffel::DecodeScalar<S>
|
||||
for FloatOrInt<MIN, MAX>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
pub trait MergeWith<T> {
|
||||
fn merge_with(&mut self, part: &T);
|
||||
|
||||
fn merged_with(mut self, part: &T) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.merge_with(part);
|
||||
self
|
||||
}
|
||||
|
||||
fn from_part(part: &T) -> Self
|
||||
where
|
||||
Self: Default + Sized,
|
||||
{
|
||||
Self::default().merged_with(part)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
use niri_ipc::ColumnDisplay;
|
||||
|
||||
use crate::appearance::{BlockOutFrom, BorderRule, CornerRadius, ShadowRule, TabIndicatorRule};
|
||||
use crate::appearance::{
|
||||
BackgroundEffect, BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule,
|
||||
TabIndicatorRule,
|
||||
};
|
||||
use crate::layout::DefaultPresetSize;
|
||||
use crate::utils::RegexEq;
|
||||
use crate::utils::{MergeWith, RegexEq};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
@@ -24,6 +27,8 @@ pub struct WindowRule {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_maximized: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_maximized_to_edges: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_fullscreen: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_floating: Option<bool>,
|
||||
@@ -70,6 +75,46 @@ pub struct WindowRule {
|
||||
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub tiled_state: Option<bool>,
|
||||
#[knuffel(child, default)]
|
||||
pub background_effect: BackgroundEffectRule,
|
||||
#[knuffel(child, default)]
|
||||
pub popups: PopupsRule,
|
||||
}
|
||||
|
||||
/// Rules for popup surfaces.
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct PopupsRule {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub opacity: Option<f32>,
|
||||
#[knuffel(child)]
|
||||
pub geometry_corner_radius: Option<CornerRadius>,
|
||||
#[knuffel(child, default)]
|
||||
pub background_effect: BackgroundEffectRule,
|
||||
}
|
||||
|
||||
/// Resolved popup-specific rules.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct ResolvedPopupsRules {
|
||||
/// Extra opacity to draw popups with.
|
||||
pub opacity: Option<f32>,
|
||||
|
||||
/// Corner radius to assume the popups have.
|
||||
pub geometry_corner_radius: Option<CornerRadius>,
|
||||
|
||||
/// Background effect configuration for popups.
|
||||
pub background_effect: BackgroundEffect,
|
||||
}
|
||||
|
||||
impl MergeWith<PopupsRule> for ResolvedPopupsRules {
|
||||
fn merge_with(&mut self, part: &PopupsRule) {
|
||||
if let Some(x) = part.opacity {
|
||||
self.opacity = Some(x);
|
||||
}
|
||||
if let Some(x) = part.geometry_corner_radius {
|
||||
self.geometry_corner_radius = Some(x);
|
||||
}
|
||||
self.background_effect.merge_with(&part.background_effect);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
|
||||
@@ -1,16 +1,50 @@
|
||||
use knuffel::errors::DecodeError;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
use crate::LayoutPart;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Workspace {
|
||||
#[knuffel(argument)]
|
||||
pub name: WorkspaceName,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_on_output: Option<String>,
|
||||
#[knuffel(child)]
|
||||
pub layout: Option<WorkspaceLayoutPart>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WorkspaceName(pub String);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WorkspaceLayoutPart(pub LayoutPart);
|
||||
|
||||
impl<S: knuffel::traits::ErrorSpan> knuffel::Decode<S> for WorkspaceLayoutPart {
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
for child in node.children() {
|
||||
let name = &**child.node_name;
|
||||
|
||||
// Check for disallowed properties.
|
||||
//
|
||||
// - empty-workspace-above-first is a monitor-level concept.
|
||||
// - insert-hint customization could make sense for workspaces, however currently it is
|
||||
// also handled at the monitor level (since insert hints in-between workspaces are a
|
||||
// monitor-level concept), so for now this config option would do nothing.
|
||||
if matches!(name, "empty-workspace-above-first" | "insert-hint") {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("node `{name}` is not allowed inside `workspace.layout`"),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
LayoutPart::decode_node(node, ctx).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
|
||||
fn type_check(
|
||||
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
struct KdlCodeBlock {
|
||||
filename: String,
|
||||
@@ -84,7 +84,7 @@ fn wiki_docs_parses() {
|
||||
must_fail,
|
||||
} in code_blocks
|
||||
{
|
||||
if let Err(error) = niri_config::Config::parse(&filename, &code) {
|
||||
if let Err(error) = niri_config::Config::parse(Path::new(&filename), &code).config {
|
||||
if !must_fail {
|
||||
errors.push(format!(
|
||||
"Error parsing wiki KDL code block at {}:{}: {:?}",
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true, optional = true }
|
||||
schemars = { version = "1.0.4", optional = true }
|
||||
schemars = { version = "1.2.1", optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# niri-ipc
|
||||
|
||||
Types and helpers for interfacing with the [niri](https://github.com/YaLTeR/niri) Wayland compositor.
|
||||
Types and helpers for interfacing with the [niri](https://github.com/niri-wm/niri) Wayland compositor.
|
||||
|
||||
## Backwards compatibility
|
||||
|
||||
@@ -12,5 +12,5 @@ Use an exact version requirement to avoid breaking changes:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
niri-ipc = "=25.8.0"
|
||||
niri-ipc = "=26.4.0"
|
||||
```
|
||||
|
||||
+439
-17
@@ -41,7 +41,7 @@
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! niri-ipc = "=25.8.0"
|
||||
//! niri-ipc = "=26.4.0"
|
||||
//! ```
|
||||
//!
|
||||
//! ## Features
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -116,6 +117,8 @@ pub enum Request {
|
||||
ReturnError,
|
||||
/// Request information about the overview.
|
||||
OverviewState,
|
||||
/// Request information about screencasts.
|
||||
Casts,
|
||||
}
|
||||
|
||||
/// Reply from niri to client.
|
||||
@@ -160,6 +163,8 @@ pub enum Response {
|
||||
OutputConfigChanged(OutputConfigChanged),
|
||||
/// Information about the overview.
|
||||
OverviewState(Overview),
|
||||
/// Information about screencasts.
|
||||
Casts(Vec<Cast>),
|
||||
}
|
||||
|
||||
/// Overview information.
|
||||
@@ -220,6 +225,14 @@ pub enum Action {
|
||||
/// Whether to show the mouse pointer by default in the screenshot UI.
|
||||
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||
show_pointer: bool,
|
||||
|
||||
/// Path to save the screenshot to.
|
||||
///
|
||||
/// The path must be absolute, otherwise an error is returned.
|
||||
///
|
||||
/// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
|
||||
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
|
||||
path: Option<String>,
|
||||
},
|
||||
/// Screenshot the focused screen.
|
||||
ScreenshotScreen {
|
||||
@@ -232,6 +245,14 @@ pub enum Action {
|
||||
/// Whether to include the mouse pointer in the screenshot.
|
||||
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||
show_pointer: bool,
|
||||
|
||||
/// Path to save the screenshot to.
|
||||
///
|
||||
/// The path must be absolute, otherwise an error is returned.
|
||||
///
|
||||
/// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
|
||||
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
|
||||
path: Option<String>,
|
||||
},
|
||||
/// Screenshot a window.
|
||||
#[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))]
|
||||
@@ -246,6 +267,21 @@ pub enum Action {
|
||||
/// The screenshot is saved according to the `screenshot-path` config setting.
|
||||
#[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||
write_to_disk: bool,
|
||||
|
||||
/// Whether to include the mouse pointer in the screenshot.
|
||||
///
|
||||
/// The pointer will be included only if the window is currently receiving pointer input
|
||||
/// (usually this means the pointer is on top of the window).
|
||||
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = false))]
|
||||
show_pointer: bool,
|
||||
|
||||
/// Path to save the screenshot to.
|
||||
///
|
||||
/// The path must be absolute, otherwise an error is returned.
|
||||
///
|
||||
/// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
|
||||
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
|
||||
path: Option<String>,
|
||||
},
|
||||
/// Enable or disable the keyboard shortcuts inhibitor (if any) for the focused surface.
|
||||
ToggleKeyboardShortcutsInhibit {},
|
||||
@@ -404,7 +440,7 @@ pub enum Action {
|
||||
},
|
||||
/// Consume the window to the right into the focused column.
|
||||
ConsumeWindowIntoColumn {},
|
||||
/// Expel the focused window from the column.
|
||||
/// Expel the bottom window from the focused column.
|
||||
ExpelWindowFromColumn {},
|
||||
/// Swap focused window with one to the right.
|
||||
SwapWindowRight {},
|
||||
@@ -713,6 +749,14 @@ pub enum Action {
|
||||
},
|
||||
/// Toggle the maximized state of the focused column.
|
||||
MaximizeColumn {},
|
||||
/// Toggle the maximized-to-edges state of the focused window.
|
||||
MaximizeWindowToEdges {
|
||||
/// Id of the window to maximize.
|
||||
///
|
||||
/// If `None`, uses the focused window.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: Option<u64>,
|
||||
},
|
||||
/// Change the width of the focused column.
|
||||
SetColumnWidth {
|
||||
/// How to change the width.
|
||||
@@ -805,14 +849,14 @@ pub enum Action {
|
||||
/// How to change the X position.
|
||||
#[cfg_attr(
|
||||
feature = "clap",
|
||||
arg(short, long, default_value = "+0", allow_negative_numbers = true)
|
||||
arg(short, long, default_value = "+0", allow_hyphen_values = true)
|
||||
)]
|
||||
x: PositionChange,
|
||||
|
||||
/// How to change the Y position.
|
||||
#[cfg_attr(
|
||||
feature = "clap",
|
||||
arg(short, long, default_value = "+0", allow_negative_numbers = true)
|
||||
arg(short, long, default_value = "+0", allow_hyphen_values = true)
|
||||
)]
|
||||
y: PositionChange,
|
||||
},
|
||||
@@ -854,6 +898,16 @@ pub enum Action {
|
||||
},
|
||||
/// Clear the dynamic cast target, making it show nothing.
|
||||
ClearDynamicCastTarget {},
|
||||
/// Stop a PipeWire screencast.
|
||||
///
|
||||
/// wlr-screencopy screencasts cannot currently be stopped via IPC.
|
||||
StopCast {
|
||||
/// Session ID of the screencast to stop.
|
||||
///
|
||||
/// If the session has multiple screencast streams, this will stop all of them.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
session_id: u64,
|
||||
},
|
||||
/// Toggle (open/close) the Overview.
|
||||
ToggleOverview {},
|
||||
/// Open the Overview.
|
||||
@@ -882,7 +936,13 @@ pub enum Action {
|
||||
///
|
||||
/// Can be useful for scripts changing the config file, to avoid waiting the small duration for
|
||||
/// niri's config file watcher to notice the changes.
|
||||
LoadConfigFile {},
|
||||
LoadConfigFile {
|
||||
/// Path of a new config file to load.
|
||||
///
|
||||
/// If unset, reloads the current config file.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
path: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Change in window or column size.
|
||||
@@ -905,8 +965,12 @@ pub enum SizeChange {
|
||||
pub enum PositionChange {
|
||||
/// Set the position in logical pixels.
|
||||
SetFixed(f64),
|
||||
/// Set the position as a proportion of the working area.
|
||||
SetProportion(f64),
|
||||
/// Add or subtract to the current position in logical pixels.
|
||||
AdjustFixed(f64),
|
||||
/// Add or subtract to the current position as a proportion of the working area.
|
||||
AdjustProportion(f64),
|
||||
}
|
||||
|
||||
/// Workspace reference (id, index or name) to operate on.
|
||||
@@ -964,6 +1028,51 @@ pub enum OutputAction {
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
mode: ModeToSet,
|
||||
},
|
||||
/// Set a custom output mode.
|
||||
CustomMode {
|
||||
/// Custom mode to set.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
mode: ConfiguredMode,
|
||||
},
|
||||
/// Set a custom VESA CVT modeline.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
Modeline {
|
||||
/// The rate at which pixels are drawn in MHz.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
clock: f64,
|
||||
/// Horizontal active pixels.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
hdisplay: u16,
|
||||
/// Horizontal sync pulse start position in pixels.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
hsync_start: u16,
|
||||
/// Horizontal sync pulse end position in pixels.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
hsync_end: u16,
|
||||
/// Total horizontal number of pixels before resetting the horizontal drawing position to
|
||||
/// zero.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
htotal: u16,
|
||||
|
||||
/// Vertical active pixels.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
vdisplay: u16,
|
||||
/// Vertical sync pulse start position in pixels.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
vsync_start: u16,
|
||||
/// Vertical sync pulse end position in pixels.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
vsync_end: u16,
|
||||
/// Total vertical number of pixels before resetting the vertical drawing position to zero.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
vtotal: u16,
|
||||
/// Horizontal sync polarity: "+hsync" or "-hsync".
|
||||
#[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
|
||||
hsync_polarity: HSyncPolarity,
|
||||
/// Vertical sync polarity: "+vsync" or "-vsync".
|
||||
#[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
|
||||
vsync_polarity: VSyncPolarity,
|
||||
},
|
||||
/// Set the output scale.
|
||||
Scale {
|
||||
/// Scale factor to set, or "auto" for automatic selection.
|
||||
@@ -1012,6 +1121,26 @@ pub struct ConfiguredMode {
|
||||
pub refresh: Option<f64>,
|
||||
}
|
||||
|
||||
/// Modeline horizontal syncing polarity.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub enum HSyncPolarity {
|
||||
/// Positive polarity.
|
||||
PHSync,
|
||||
/// Negative polarity.
|
||||
NHSync,
|
||||
}
|
||||
|
||||
/// Modeline vertical syncing polarity.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub enum VSyncPolarity {
|
||||
/// Positive polarity.
|
||||
PVSync,
|
||||
/// Negative polarity.
|
||||
NVSync,
|
||||
}
|
||||
|
||||
/// Output scale to set.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
@@ -1089,6 +1218,8 @@ pub struct Output {
|
||||
///
|
||||
/// `None` if the output is disabled.
|
||||
pub current_mode: Option<usize>,
|
||||
/// Whether the current_mode is a custom mode.
|
||||
pub is_custom_mode: bool,
|
||||
/// Whether the output supports variable refresh rate.
|
||||
pub vrr_supported: bool,
|
||||
/// Whether variable refresh rate is enabled on the output.
|
||||
@@ -1195,6 +1326,24 @@ pub struct Window {
|
||||
pub is_urgent: bool,
|
||||
/// Position- and size-related properties of the window.
|
||||
pub layout: WindowLayout,
|
||||
/// Timestamp when the window was most recently focused.
|
||||
///
|
||||
/// This timestamp is intended for most-recently-used window switchers, i.e. Alt-Tab. It only
|
||||
/// updates after some debounce time so that quick window switching doesn't mark intermediate
|
||||
/// windows as recently focused.
|
||||
///
|
||||
/// The timestamp comes from the monotonic clock.
|
||||
pub focus_timestamp: Option<Timestamp>,
|
||||
}
|
||||
|
||||
/// A moment in time.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub struct Timestamp {
|
||||
/// Number of whole seconds.
|
||||
pub secs: u64,
|
||||
/// Fractional part of the timestamp in nanoseconds (10<sup>-9</sup> seconds).
|
||||
pub nanos: u32,
|
||||
}
|
||||
|
||||
/// Position- and size-related properties of a [`Window`].
|
||||
@@ -1344,6 +1493,78 @@ pub struct LayerSurface {
|
||||
pub keyboard_interactivity: LayerSurfaceKeyboardInteractivity,
|
||||
}
|
||||
|
||||
/// A screencast.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub struct Cast {
|
||||
/// Stream ID of the screencast that uniquely identifies it.
|
||||
pub stream_id: u64,
|
||||
/// Session ID of the screencast.
|
||||
///
|
||||
/// A session can have multiple screencast streams. Then multiple `Cast`s will have the same
|
||||
/// `session_id`. Though, usually there's only one stream per session.
|
||||
///
|
||||
/// Do not confuse `session_id` with [`stream_id`](Self::stream_id).
|
||||
pub session_id: u64,
|
||||
/// Kind of this screencast.
|
||||
pub kind: CastKind,
|
||||
/// Target being captured.
|
||||
pub target: CastTarget,
|
||||
/// Whether this is a Dynamic Cast Target screencast.
|
||||
///
|
||||
/// Meaning that actions like `SetDynamicCastWindow` will act on this screencast.
|
||||
///
|
||||
/// Keep in mind that the target can change even if this is `false`.
|
||||
pub is_dynamic_target: bool,
|
||||
/// Whether the cast is currently streaming frames.
|
||||
///
|
||||
/// This can be `false` for example when switching away to a different scene in OBS, which
|
||||
/// pauses the stream.
|
||||
pub is_active: bool,
|
||||
/// Process ID of the screencast consumer, if known.
|
||||
///
|
||||
/// Currently, only wlr-screencopy screencasts can have a pid.
|
||||
pub pid: Option<i32>,
|
||||
/// PipeWire node ID of the screencast stream.
|
||||
///
|
||||
/// This is `None` for wlr-screencopy casts, and also for PipeWire casts before the node is
|
||||
/// created (when the cast is just starting up).
|
||||
pub pw_node_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// Kind of screencast.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub enum CastKind {
|
||||
/// PipeWire screencast, typically via xdg-desktop-portal-gnome.
|
||||
PipeWire,
|
||||
/// wlr-screencopy protocol screencast.
|
||||
///
|
||||
/// Tools like wf-recorder, and the xdg-desktop-portal-wlr portal.
|
||||
///
|
||||
/// Only wlr-screencopy with damage tracking is reported here. Screencopy without damage is
|
||||
/// treated as a regular screenshot and not reported as a screencast.
|
||||
WlrScreencopy,
|
||||
}
|
||||
|
||||
/// Target of a screencast.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub enum CastTarget {
|
||||
/// The target is not yet set, or was cleared.
|
||||
Nothing {},
|
||||
/// Casting an output.
|
||||
Output {
|
||||
/// Name of the screencasted output.
|
||||
name: String,
|
||||
},
|
||||
/// Casting a window.
|
||||
Window {
|
||||
/// ID of the screencasted window.
|
||||
id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// A compositor event.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
@@ -1410,6 +1631,17 @@ pub enum Event {
|
||||
/// Id of the newly focused window, or `None` if no window is now focused.
|
||||
id: Option<u64>,
|
||||
},
|
||||
/// Window focus timestamp changed.
|
||||
///
|
||||
/// This event is separate from [`Event::WindowFocusChanged`] because the focus timestamp only
|
||||
/// updates after some debounce time so that quick window switching doesn't mark intermediate
|
||||
/// windows as recently focused.
|
||||
WindowFocusTimestampChanged {
|
||||
/// Id of the window.
|
||||
id: u64,
|
||||
/// The new focus timestamp.
|
||||
focus_timestamp: Option<Timestamp>,
|
||||
},
|
||||
/// Window urgency changed.
|
||||
WindowUrgencyChanged {
|
||||
/// Id of the window.
|
||||
@@ -1447,6 +1679,47 @@ pub enum Event {
|
||||
/// For example, the config file couldn't be parsed.
|
||||
failed: bool,
|
||||
},
|
||||
/// A screenshot was captured.
|
||||
ScreenshotCaptured {
|
||||
/// The file path where the screenshot was saved, if it was written to disk.
|
||||
///
|
||||
/// If `None`, the screenshot was either only copied to the clipboard, or the path couldn't
|
||||
/// be converted to a `String` (e.g. contained invalid UTF-8 bytes).
|
||||
path: Option<String>,
|
||||
},
|
||||
/// The screencasts have changed.
|
||||
CastsChanged {
|
||||
/// The new screencast information.
|
||||
///
|
||||
/// This configuration completely replaces the previous configuration. I.e. if any casts
|
||||
/// are missing from here, then they were stopped.
|
||||
casts: Vec<Cast>,
|
||||
},
|
||||
/// A screencast started, or an existing cast changed.
|
||||
CastStartedOrChanged {
|
||||
/// The cast that started or changed.
|
||||
cast: Cast,
|
||||
},
|
||||
/// A screencast stopped.
|
||||
CastStopped {
|
||||
/// Stream ID of the stopped screencast.
|
||||
stream_id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Duration> for Timestamp {
|
||||
fn from(value: Duration) -> Self {
|
||||
Timestamp {
|
||||
secs: value.as_secs(),
|
||||
nanos: value.subsec_nanos(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Timestamp> for Duration {
|
||||
fn from(value: Timestamp) -> Self {
|
||||
Duration::new(value.secs, value.nanos)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for WorkspaceReferenceArg {
|
||||
@@ -1511,17 +1784,38 @@ impl FromStr for PositionChange {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let value = s;
|
||||
match value.bytes().next() {
|
||||
Some(b'-' | b'+') => {
|
||||
let value = value.parse().map_err(|_| "error parsing value")?;
|
||||
Ok(Self::AdjustFixed(value))
|
||||
match s.split_once('%') {
|
||||
Some((value, empty)) => {
|
||||
if !empty.is_empty() {
|
||||
return Err("trailing characters after '%' are not allowed");
|
||||
}
|
||||
|
||||
match value.bytes().next() {
|
||||
Some(b'-' | b'+') => {
|
||||
let value = value.parse().map_err(|_| "error parsing value")?;
|
||||
Ok(Self::AdjustProportion(value))
|
||||
}
|
||||
Some(_) => {
|
||||
let value = value.parse().map_err(|_| "error parsing value")?;
|
||||
Ok(Self::SetProportion(value))
|
||||
}
|
||||
None => Err("value is missing"),
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
let value = value.parse().map_err(|_| "error parsing value")?;
|
||||
Ok(Self::SetFixed(value))
|
||||
None => {
|
||||
let value = s;
|
||||
match value.bytes().next() {
|
||||
Some(b'-' | b'+') => {
|
||||
let value = value.parse().map_err(|_| "error parsing value")?;
|
||||
Ok(Self::AdjustFixed(value))
|
||||
}
|
||||
Some(_) => {
|
||||
let value = value.parse().map_err(|_| "error parsing value")?;
|
||||
Ok(Self::SetFixed(value))
|
||||
}
|
||||
None => Err("value is missing"),
|
||||
}
|
||||
}
|
||||
None => Err("value is missing"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1574,6 +1868,20 @@ impl FromStr for Transform {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Layer {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"background" => Ok(Self::Background),
|
||||
"bottom" => Ok(Self::Bottom),
|
||||
"top" => Ok(Self::Top),
|
||||
"overlay" => Ok(Self::Overlay),
|
||||
_ => Err("invalid layer, can be \"background\", \"bottom\", \"top\" or \"overlay\""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ModeToSet {
|
||||
type Err = &'static str;
|
||||
|
||||
@@ -1615,6 +1923,30 @@ impl FromStr for ConfiguredMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for HSyncPolarity {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"+hsync" => Ok(Self::PHSync),
|
||||
"-hsync" => Ok(Self::NHSync),
|
||||
_ => Err(r#"invalid horizontal sync polarity, can be "+hsync" or "-hsync"#),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VSyncPolarity {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"+vsync" => Ok(Self::PVSync),
|
||||
"-vsync" => Ok(Self::NVSync),
|
||||
_ => Err(r#"invalid vertical sync polarity, can be "+vsync" or "-vsync"#),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ScaleToSet {
|
||||
type Err = &'static str;
|
||||
|
||||
@@ -1628,6 +1960,87 @@ impl FromStr for ScaleToSet {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $fmt:literal $($arg:tt)* ) => {
|
||||
if !$cond {
|
||||
return Err(format!($fmt $($arg)*));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl OutputAction {
|
||||
/// Validates some required constraints on the modeline and custom mode.
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
match self {
|
||||
OutputAction::Modeline {
|
||||
hdisplay,
|
||||
hsync_start,
|
||||
hsync_end,
|
||||
htotal,
|
||||
vdisplay,
|
||||
vsync_start,
|
||||
vsync_end,
|
||||
vtotal,
|
||||
..
|
||||
} => {
|
||||
ensure!(
|
||||
hdisplay < hsync_start,
|
||||
"hdisplay {} must be < hsync_start {}",
|
||||
hdisplay,
|
||||
hsync_start
|
||||
);
|
||||
ensure!(
|
||||
hsync_start < hsync_end,
|
||||
"hsync_start {} must be < hsync_end {}",
|
||||
hsync_start,
|
||||
hsync_end
|
||||
);
|
||||
ensure!(
|
||||
hsync_end < htotal,
|
||||
"hsync_end {} must be < htotal {}",
|
||||
hsync_end,
|
||||
htotal
|
||||
);
|
||||
ensure!(0 < *htotal, "htotal {} must be > 0", htotal);
|
||||
ensure!(
|
||||
vdisplay < vsync_start,
|
||||
"vdisplay {} must be < vsync_start {}",
|
||||
vdisplay,
|
||||
vsync_start
|
||||
);
|
||||
ensure!(
|
||||
vsync_start < vsync_end,
|
||||
"vsync_start {} must be < vsync_end {}",
|
||||
vsync_start,
|
||||
vsync_end
|
||||
);
|
||||
ensure!(
|
||||
vsync_end < vtotal,
|
||||
"vsync_end {} must be < vtotal {}",
|
||||
vsync_end,
|
||||
vtotal
|
||||
);
|
||||
ensure!(0 < *vtotal, "vtotal {} must be > 0", vtotal);
|
||||
Ok(())
|
||||
}
|
||||
OutputAction::CustomMode {
|
||||
mode: ConfiguredMode { refresh, .. },
|
||||
} => {
|
||||
if refresh.is_none() {
|
||||
return Err("refresh rate is required for custom modes".to_string());
|
||||
}
|
||||
if let Some(refresh) = refresh {
|
||||
if *refresh <= 0. {
|
||||
return Err(format!("custom mode refresh rate {refresh} must be > 0"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1678,9 +2091,18 @@ mod tests {
|
||||
PositionChange::AdjustFixed(-10.),
|
||||
);
|
||||
|
||||
assert!("10%".parse::<PositionChange>().is_err());
|
||||
assert!("+10%".parse::<PositionChange>().is_err());
|
||||
assert!("-10%".parse::<PositionChange>().is_err());
|
||||
assert_eq!(
|
||||
"10%".parse::<PositionChange>().unwrap(),
|
||||
PositionChange::SetProportion(10.)
|
||||
);
|
||||
assert_eq!(
|
||||
"+10%".parse::<PositionChange>().unwrap(),
|
||||
PositionChange::AdjustProportion(10.)
|
||||
);
|
||||
assert_eq!(
|
||||
"-10%".parse::<PositionChange>().unwrap(),
|
||||
PositionChange::AdjustProportion(-10.)
|
||||
);
|
||||
assert!("-".parse::<PositionChange>().is_err());
|
||||
assert!("10% ".parse::<PositionChange>().is_err());
|
||||
}
|
||||
|
||||
+48
-1
@@ -9,7 +9,7 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{Event, KeyboardLayouts, Window, Workspace};
|
||||
use crate::{Cast, Event, KeyboardLayouts, Window, Workspace};
|
||||
|
||||
/// Part of the state communicated via the event stream.
|
||||
pub trait EventStreamStatePart {
|
||||
@@ -46,6 +46,9 @@ pub struct EventStreamState {
|
||||
|
||||
/// State of the config.
|
||||
pub config: ConfigState,
|
||||
|
||||
/// State of screencasts.
|
||||
pub casts: CastsState,
|
||||
}
|
||||
|
||||
/// The workspaces state communicated over the event stream.
|
||||
@@ -83,6 +86,13 @@ pub struct ConfigState {
|
||||
pub failed: bool,
|
||||
}
|
||||
|
||||
/// The casts state communicated over the event stream.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CastsState {
|
||||
/// Map from a stream id to the screencast.
|
||||
pub casts: HashMap<u64, Cast>,
|
||||
}
|
||||
|
||||
impl EventStreamStatePart for EventStreamState {
|
||||
fn replicate(&self) -> Vec<Event> {
|
||||
let mut events = Vec::new();
|
||||
@@ -91,6 +101,7 @@ impl EventStreamStatePart for EventStreamState {
|
||||
events.extend(self.keyboard_layouts.replicate());
|
||||
events.extend(self.overview.replicate());
|
||||
events.extend(self.config.replicate());
|
||||
events.extend(self.casts.replicate());
|
||||
events
|
||||
}
|
||||
|
||||
@@ -100,6 +111,7 @@ impl EventStreamStatePart for EventStreamState {
|
||||
let event = self.keyboard_layouts.apply(event)?;
|
||||
let event = self.overview.apply(event)?;
|
||||
let event = self.config.apply(event)?;
|
||||
let event = self.casts.apply(event)?;
|
||||
Some(event)
|
||||
}
|
||||
}
|
||||
@@ -193,6 +205,17 @@ impl EventStreamStatePart for WindowsState {
|
||||
win.is_focused = Some(win.id) == id;
|
||||
}
|
||||
}
|
||||
Event::WindowFocusTimestampChanged {
|
||||
id,
|
||||
focus_timestamp,
|
||||
} => {
|
||||
for win in self.windows.values_mut() {
|
||||
if win.id == id {
|
||||
win.focus_timestamp = focus_timestamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::WindowUrgencyChanged { id, urgent } => {
|
||||
for win in self.windows.values_mut() {
|
||||
if win.id == id {
|
||||
@@ -274,3 +297,27 @@ impl EventStreamStatePart for ConfigState {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl EventStreamStatePart for CastsState {
|
||||
fn replicate(&self) -> Vec<Event> {
|
||||
let casts = self.casts.values().cloned().collect();
|
||||
vec![Event::CastsChanged { casts }]
|
||||
}
|
||||
|
||||
fn apply(&mut self, event: Event) -> Option<Event> {
|
||||
match event {
|
||||
Event::CastsChanged { casts } => {
|
||||
self.casts = casts.into_iter().map(|c| (c.stream_id, c)).collect();
|
||||
}
|
||||
Event::CastStartedOrChanged { cast } => {
|
||||
self.casts.insert(cast.stream_id, cast);
|
||||
}
|
||||
Event::CastStopped { stream_id } => {
|
||||
let cast = self.casts.remove(&stream_id);
|
||||
cast.expect("stopped cast was missing from the map");
|
||||
}
|
||||
event => return Some(event),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adw = { version = "0.7.2", package = "libadwaita", features = ["v1_4"] }
|
||||
adw = { version = "0.8.1", package = "libadwaita", features = ["v1_4"] }
|
||||
anyhow.workspace = true
|
||||
gtk = { version = "0.9.7", package = "gtk4", features = ["v4_12"] }
|
||||
niri = { version = "25.8.0", path = ".." }
|
||||
niri-config = { version = "25.8.0", path = "../niri-config" }
|
||||
gtk = { version = "0.10.3", package = "gtk4", features = ["v4_12"] }
|
||||
niri = { version = "26.4.0", path = ".." }
|
||||
niri-config = { version = "26.4.0", path = "../niri-config" }
|
||||
smithay.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use niri::layout::focus_ring::FocusRing;
|
||||
use niri::render_helpers::border::BorderRenderElement;
|
||||
use niri_config::{Color, CornerRadius, FloatOrInt, GradientInterpolation};
|
||||
use niri_config::{Color, CornerRadius, GradientInterpolation};
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Physical, Point, Rectangle, Size};
|
||||
@@ -20,7 +20,7 @@ impl GradientArea {
|
||||
pub fn new(_args: Args) -> Self {
|
||||
let border = FocusRing::new(niri_config::FocusRing {
|
||||
off: false,
|
||||
width: FloatOrInt(1.),
|
||||
width: 1.,
|
||||
active_color: Color::from_rgba8_unpremul(255, 255, 255, 128),
|
||||
inactive_color: Color::default(),
|
||||
urgent_color: Color::default(),
|
||||
@@ -89,11 +89,8 @@ impl TestCase for GradientArea {
|
||||
1.,
|
||||
1.,
|
||||
);
|
||||
rv.extend(
|
||||
self.border
|
||||
.render(renderer, g_loc)
|
||||
.map(|elem| Box::new(elem) as _),
|
||||
);
|
||||
self.border
|
||||
.render(renderer, g_loc, &mut |elem| rv.push(Box::new(elem) as _));
|
||||
|
||||
rv.extend(
|
||||
[BorderRenderElement::new(
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri::animation::Clock;
|
||||
use niri::layout::{ActivateWindow, AddWindowTarget, LayoutElement as _, Options};
|
||||
use niri::render_helpers::RenderTarget;
|
||||
use niri_config::{Color, FloatOrInt, OutputName, PresetSize};
|
||||
use niri::layout::{ActivateWindow, AddWindowTarget, LayoutElement as _, Options, SizingMode};
|
||||
use niri::render_helpers::{RenderCtx, RenderTarget};
|
||||
use niri_config::{Color, OutputName, PresetSize};
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::desktop::layer_map_for_output;
|
||||
@@ -52,24 +52,27 @@ impl Layout {
|
||||
});
|
||||
|
||||
let options = Options {
|
||||
focus_ring: niri_config::FocusRing {
|
||||
off: true,
|
||||
layout: niri_config::Layout {
|
||||
focus_ring: niri_config::FocusRing {
|
||||
off: true,
|
||||
..Default::default()
|
||||
},
|
||||
border: niri_config::Border {
|
||||
off: false,
|
||||
width: 4.,
|
||||
active_color: Color::from_rgba8_unpremul(255, 163, 72, 255),
|
||||
inactive_color: Color::from_rgba8_unpremul(50, 50, 50, 255),
|
||||
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
urgent_gradient: None,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
border: niri_config::Border {
|
||||
off: false,
|
||||
width: FloatOrInt(4.),
|
||||
active_color: Color::from_rgba8_unpremul(255, 163, 72, 255),
|
||||
inactive_color: Color::from_rgba8_unpremul(50, 50, 50, 255),
|
||||
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
urgent_gradient: None,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut layout = niri::layout::Layout::with_options(clock.clone(), options);
|
||||
layout.add_output(output.clone());
|
||||
layout.add_output(output.clone(), None);
|
||||
|
||||
let start_time = clock.now_unadjusted();
|
||||
|
||||
@@ -165,7 +168,7 @@ impl Layout {
|
||||
let max_size = window.max_size();
|
||||
window.request_size(
|
||||
ws.new_window_size(width, None, false, window.rules(), (min_size, max_size)),
|
||||
false,
|
||||
SizingMode::Normal,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
@@ -194,7 +197,7 @@ impl Layout {
|
||||
let max_size = window.max_size();
|
||||
window.request_size(
|
||||
ws.new_window_size(width, None, false, window.rules(), (min_size, max_size)),
|
||||
false,
|
||||
SizingMode::Normal,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
@@ -265,12 +268,17 @@ impl TestCase for Layout {
|
||||
_size: Size<i32, Physical>,
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||
self.layout.update_render_elements(Some(&self.output));
|
||||
|
||||
let mut rv = Vec::new();
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
self.layout
|
||||
.monitor_for_output(&self.output)
|
||||
.unwrap()
|
||||
.render_elements(renderer, RenderTarget::Output, true)
|
||||
.flat_map(|(_, iter)| iter)
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
.render_workspaces(ctx, true, &mut |elem| rv.push(Box::new(elem) as _));
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri::layout::Options;
|
||||
use niri::render_helpers::RenderTarget;
|
||||
use niri_config::{Color, FloatOrInt};
|
||||
use niri::render_helpers::xray::XrayPos;
|
||||
use niri::render_helpers::{RenderCtx, RenderTarget};
|
||||
use niri_config::Color;
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Physical, Point, Rectangle, Size};
|
||||
@@ -58,14 +59,17 @@ impl Tile {
|
||||
let Args { size, clock } = args;
|
||||
|
||||
let options = Options {
|
||||
focus_ring: niri_config::FocusRing {
|
||||
off: true,
|
||||
..Default::default()
|
||||
},
|
||||
border: niri_config::Border {
|
||||
off: false,
|
||||
width: FloatOrInt(32.),
|
||||
active_color: Color::from_rgba8_unpremul(255, 163, 72, 255),
|
||||
layout: niri_config::Layout {
|
||||
focus_ring: niri_config::FocusRing {
|
||||
off: true,
|
||||
..Default::default()
|
||||
},
|
||||
border: niri_config::Border {
|
||||
off: false,
|
||||
width: 32.,
|
||||
active_color: Color::from_rgba8_unpremul(255, 163, 72, 255),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -116,9 +120,18 @@ impl TestCase for Tile {
|
||||
true,
|
||||
Rectangle::new(Point::from((-location.x, -location.y)), size.to_logical(1.)),
|
||||
);
|
||||
|
||||
let mut rv = Vec::new();
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
let xray_pos = XrayPos::new(location, 1.);
|
||||
self.tile
|
||||
.render(renderer, location, true, RenderTarget::Output)
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
.render(ctx, location, xray_pos, true, &mut |elem| {
|
||||
rv.push(Box::new(elem) as _)
|
||||
});
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use niri::layout::LayoutElement;
|
||||
use niri::render_helpers::RenderTarget;
|
||||
use niri::layout::{LayoutElement, SizingMode};
|
||||
use niri::render_helpers::{RenderCtx, RenderTarget};
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Physical, Point, Scale, Size};
|
||||
@@ -14,14 +14,14 @@ pub struct Window {
|
||||
impl Window {
|
||||
pub fn freeform(args: Args) -> Self {
|
||||
let mut window = TestWindow::freeform(0);
|
||||
window.request_size(args.size, false, false, None);
|
||||
window.request_size(args.size, SizingMode::Normal, false, None);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn fixed_size(args: Args) -> Self {
|
||||
let mut window = TestWindow::fixed_size(0);
|
||||
window.request_size(args.size, false, false, None);
|
||||
window.request_size(args.size, SizingMode::Normal, false, None);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
@@ -29,7 +29,7 @@ impl Window {
|
||||
pub fn fixed_size_with_csd_shadow(args: Args) -> Self {
|
||||
let mut window = TestWindow::fixed_size(0);
|
||||
window.set_csd_shadow_width(64);
|
||||
window.request_size(args.size, false, false, None);
|
||||
window.request_size(args.size, SizingMode::Normal, false, None);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
@@ -38,7 +38,7 @@ impl Window {
|
||||
impl TestCase for Window {
|
||||
fn resize(&mut self, width: i32, height: i32) {
|
||||
self.window
|
||||
.request_size(Size::from((width, height)), false, false, None);
|
||||
.request_size(Size::from((width, height)), SizingMode::Normal, false, None);
|
||||
self.window.communicate();
|
||||
}
|
||||
|
||||
@@ -52,16 +52,16 @@ impl TestCase for Window {
|
||||
.to_f64()
|
||||
.downscale(2.);
|
||||
|
||||
let mut rv = Vec::new();
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
self.window
|
||||
.render(
|
||||
renderer,
|
||||
location,
|
||||
Scale::from(1.),
|
||||
1.,
|
||||
RenderTarget::Output,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
.render_normal(ctx, location, Scale::from(1.), 1., &mut |elem| {
|
||||
rv.push(Box::new(elem) as _)
|
||||
});
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ mod imp {
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::{Bind, Color32F, Frame, Offscreen, Renderer};
|
||||
use smithay::reexports::gbm::Format as Fourcc;
|
||||
use smithay::utils::user_data::UserDataMap;
|
||||
use smithay::utils::{Physical, Rectangle, Scale, Transform};
|
||||
|
||||
use super::*;
|
||||
@@ -206,8 +207,15 @@ mod imp {
|
||||
|
||||
if let Some(mut damage) = rect.intersection(dst) {
|
||||
damage.loc -= dst.loc;
|
||||
|
||||
let cache = UserDataMap::new();
|
||||
if element.is_framebuffer_effect() {
|
||||
element
|
||||
.capture_framebuffer(&mut frame, src, dst, &cache)
|
||||
.context("error in capture_framebuffer()")?;
|
||||
}
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[damage], &[])
|
||||
.draw(&mut frame, src, dst, &[damage], &[], Some(&cache))
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
}
|
||||
@@ -255,7 +263,8 @@ mod imp {
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SmithayView(ObjectSubclass<imp::SmithayView>)
|
||||
@extends gtk::Widget;
|
||||
@extends gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
impl SmithayView {
|
||||
|
||||
@@ -4,12 +4,12 @@ use std::rc::Rc;
|
||||
|
||||
use niri::layout::{
|
||||
ConfigureIntent, InteractiveResizeData, LayoutElement, LayoutElementRenderElement,
|
||||
LayoutElementRenderSnapshot,
|
||||
LayoutElementRenderSnapshot, SizingMode,
|
||||
};
|
||||
use niri::render_helpers::offscreen::OffscreenData;
|
||||
use niri::render_helpers::renderer::NiriRenderer;
|
||||
use niri::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use niri::render_helpers::{RenderTarget, SplitElements};
|
||||
use niri::render_helpers::RenderCtx;
|
||||
use niri::utils::transaction::Transaction;
|
||||
use niri::window::ResolvedWindowRules;
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
@@ -24,7 +24,7 @@ struct TestWindowInner {
|
||||
min_size: Size<i32, Logical>,
|
||||
max_size: Size<i32, Logical>,
|
||||
buffer: SolidColorBuffer,
|
||||
pending_fullscreen: bool,
|
||||
pending_sizing_mode: SizingMode,
|
||||
csd_shadow_width: i32,
|
||||
csd_shadow_buffer: SolidColorBuffer,
|
||||
}
|
||||
@@ -33,6 +33,7 @@ struct TestWindowInner {
|
||||
pub struct TestWindow {
|
||||
id: usize,
|
||||
inner: Rc<RefCell<TestWindowInner>>,
|
||||
rules: ResolvedWindowRules,
|
||||
}
|
||||
|
||||
impl TestWindow {
|
||||
@@ -50,10 +51,11 @@ impl TestWindow {
|
||||
min_size,
|
||||
max_size,
|
||||
buffer,
|
||||
pending_fullscreen: false,
|
||||
pending_sizing_mode: SizingMode::Normal,
|
||||
csd_shadow_width: 0,
|
||||
csd_shadow_buffer: SolidColorBuffer::new((0., 0.), [0., 0., 0., 0.3]),
|
||||
})),
|
||||
rules: ResolvedWindowRules::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,47 +149,40 @@ impl LayoutElement for TestWindow {
|
||||
false
|
||||
}
|
||||
|
||||
fn render<R: NiriRenderer>(
|
||||
fn render_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
_ctx: RenderCtx<R>,
|
||||
location: Point<f64, Logical>,
|
||||
_scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
_target: RenderTarget,
|
||||
) -> SplitElements<LayoutElementRenderElement<R>> {
|
||||
push: &mut dyn FnMut(LayoutElementRenderElement<R>),
|
||||
) {
|
||||
let inner = self.inner.borrow();
|
||||
|
||||
SplitElements {
|
||||
normal: vec![
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.buffer,
|
||||
location,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
push(
|
||||
SolidColorRenderElement::from_buffer(&inner.buffer, location, alpha, Kind::Unspecified)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.csd_shadow_buffer,
|
||||
location
|
||||
- Point::from((inner.csd_shadow_width, inner.csd_shadow_width)).to_f64(),
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
],
|
||||
popups: vec![],
|
||||
}
|
||||
);
|
||||
push(
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.csd_shadow_buffer,
|
||||
location - Point::from((inner.csd_shadow_width, inner.csd_shadow_width)).to_f64(),
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn request_size(
|
||||
&mut self,
|
||||
size: Size<i32, Logical>,
|
||||
is_fullscreen: bool,
|
||||
mode: SizingMode,
|
||||
_animate: bool,
|
||||
_transaction: Option<Transaction>,
|
||||
) {
|
||||
self.inner.borrow_mut().requested_size = Some(size);
|
||||
self.inner.borrow_mut().pending_fullscreen = is_fullscreen;
|
||||
self.inner.borrow_mut().pending_sizing_mode = mode;
|
||||
}
|
||||
|
||||
fn min_size(&self) -> Size<i32, Logical> {
|
||||
@@ -232,12 +227,12 @@ impl LayoutElement for TestWindow {
|
||||
|
||||
fn send_pending_configure(&mut self) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
fn pending_sizing_mode(&self) -> SizingMode {
|
||||
self.inner.borrow().pending_sizing_mode
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.inner.borrow().pending_fullscreen
|
||||
fn sizing_mode(&self) -> SizingMode {
|
||||
SizingMode::Normal
|
||||
}
|
||||
|
||||
fn requested_size(&self) -> Option<Size<i32, Logical>> {
|
||||
@@ -251,8 +246,7 @@ impl LayoutElement for TestWindow {
|
||||
fn refresh(&self) {}
|
||||
|
||||
fn rules(&self) -> &ResolvedWindowRules {
|
||||
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
|
||||
&EMPTY
|
||||
&self.rules
|
||||
}
|
||||
|
||||
fn take_animation_snapshot(&mut self) -> Option<LayoutElementRenderSnapshot> {
|
||||
|
||||
+20
-1
@@ -60,7 +60,7 @@ SourceLicense: GPL-3.0-or-later
|
||||
License: ((MIT OR Apache-2.0) AND BSD-3-Clause) AND ((MIT OR Apache-2.0) AND Unicode-3.0) AND (0BSD OR MIT OR Apache-2.0) AND (Apache-2.0) AND (Apache-2.0 AND MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR MIT) AND (Apache-2.0 OR MIT OR Unlicense) AND (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT) AND (BSD-2-Clause) AND (BSD-2-Clause OR Apache-2.0 OR MIT) AND (BSD-3-Clause OR MIT OR Apache-2.0) AND (GPL-3.0-or-later) AND (ISC) AND (MIT) AND (MIT OR Apache-2.0) AND (MIT OR Apache-2.0 OR LGPL-2.1-or-later) AND (MIT OR Apache-2.0 OR Zlib) AND (MIT OR Zlib OR Apache-2.0) AND (MPL-2.0) AND (Unicode-3.0) AND (Unlicense OR MIT) AND (Zlib) AND (Zlib OR Apache-2.0 OR MIT)
|
||||
# LICENSE.dependencies contains a full license breakdown
|
||||
|
||||
URL: https://github.com/YaLTeR/niri
|
||||
URL: https://github.com/niri-wm/niri
|
||||
VCS: {{{ git_dir_vcs }}}
|
||||
Source: {{{ git_dir_pack }}}
|
||||
|
||||
@@ -85,6 +85,14 @@ BuildRequires: mesa-libEGL
|
||||
Requires: mesa-dri-drivers
|
||||
Requires: mesa-libEGL
|
||||
|
||||
# Loaded through dlopen
|
||||
Requires: libwayland-server
|
||||
|
||||
# Integrated Xwayland support. Not packaged on EPEL
|
||||
%if 0%{?fedora}
|
||||
Requires: xwayland-satellite >= 0.7
|
||||
%endif
|
||||
|
||||
# Portal implementations used by niri
|
||||
Recommends: xdg-desktop-portal-gtk
|
||||
Recommends: xdg-desktop-portal-gnome
|
||||
@@ -123,6 +131,10 @@ sed -i 's/\[env\]/[env]\nNIRI_BUILD_COMMIT="%{version}"/' .cargo/config.toml
|
||||
%build
|
||||
%cargo_build
|
||||
|
||||
target/rpm/niri completions bash > ./niri
|
||||
target/rpm/niri completions fish > ./niri.fish
|
||||
target/rpm/niri completions zsh > ./_niri
|
||||
|
||||
%install
|
||||
%cargo_install
|
||||
|
||||
@@ -132,6 +144,10 @@ install -Dm644 -t %{buildroot}%{_datadir}/xdg-desktop-portal ./resources/niri-po
|
||||
install -Dm644 -t %{buildroot}%{_userunitdir} ./resources/niri.service
|
||||
install -Dm644 -t %{buildroot}%{_userunitdir} ./resources/niri-shutdown.target
|
||||
|
||||
install -Dm644 -t %{buildroot}%{bash_completions_dir} ./niri
|
||||
install -Dm644 -t %{buildroot}%{fish_completions_dir} ./niri.fish
|
||||
install -Dm644 -t %{buildroot}%{zsh_completions_dir} ./_niri
|
||||
|
||||
%if %{with check}
|
||||
%check
|
||||
%cargo_test -- --workspace --exclude niri-visual-tests
|
||||
@@ -149,6 +165,9 @@ install -Dm644 -t %{buildroot}%{_userunitdir} ./resources/niri-shutdown.target
|
||||
%{_datadir}/xdg-desktop-portal/niri-portals.conf
|
||||
%{_userunitdir}/niri.service
|
||||
%{_userunitdir}/niri-shutdown.target
|
||||
%{bash_completions_dir}/niri
|
||||
%{fish_completions_dir}/niri.fish
|
||||
%{zsh_completions_dir}/_niri
|
||||
|
||||
%changelog
|
||||
{{{ git_dir_changelog }}}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// This config is in the KDL format: https://kdl.dev
|
||||
// "/-" comments out the following node.
|
||||
// Check the wiki for a full description of the configuration:
|
||||
// https://yalter.github.io/niri/Configuration:-Introduction
|
||||
// https://niri-wm.github.io/niri/Configuration:-Introduction
|
||||
|
||||
// Input device configuration.
|
||||
// Find the full list of options on the wiki:
|
||||
// https://yalter.github.io/niri/Configuration:-Input
|
||||
// https://niri-wm.github.io/niri/Configuration:-Input
|
||||
input {
|
||||
keyboard {
|
||||
xkb {
|
||||
@@ -73,7 +73,7 @@ input {
|
||||
// by running `niri msg outputs` while inside a niri instance.
|
||||
// The built-in laptop monitor is usually called "eDP-1".
|
||||
// Find more information on the wiki:
|
||||
// https://yalter.github.io/niri/Configuration:-Outputs
|
||||
// https://niri-wm.github.io/niri/Configuration:-Outputs
|
||||
// Remember to uncomment the node by removing "/-"!
|
||||
/-output "eDP-1" {
|
||||
// Uncomment this line to disable this output.
|
||||
@@ -108,7 +108,7 @@ input {
|
||||
|
||||
// Settings that influence how windows are positioned and sized.
|
||||
// Find more information on the wiki:
|
||||
// https://yalter.github.io/niri/Configuration:-Layout
|
||||
// https://niri-wm.github.io/niri/Configuration:-Layout
|
||||
layout {
|
||||
// Set gaps around windows in logical pixels.
|
||||
gaps 16
|
||||
@@ -134,7 +134,7 @@ layout {
|
||||
// fixed 1920
|
||||
}
|
||||
|
||||
// You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between.
|
||||
// You can also customize the heights that "switch-preset-window-height" (Mod+Ctrl+Shift+R) toggles between.
|
||||
// preset-window-heights { }
|
||||
|
||||
// You can change the default width of the new windows.
|
||||
@@ -295,7 +295,7 @@ screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
|
||||
|
||||
// Animation settings.
|
||||
// The wiki explains how to configure individual animations:
|
||||
// https://yalter.github.io/niri/Configuration:-Animations
|
||||
// https://niri-wm.github.io/niri/Configuration:-Animations
|
||||
animations {
|
||||
// Uncomment to turn off all animations.
|
||||
// off
|
||||
@@ -306,7 +306,7 @@ animations {
|
||||
|
||||
// Window rules let you adjust behavior for individual windows.
|
||||
// Find more information on the wiki:
|
||||
// https://yalter.github.io/niri/Configuration:-Window-Rules
|
||||
// https://niri-wm.github.io/niri/Configuration:-Window-Rules
|
||||
|
||||
// Work around WezTerm's initial configure bug
|
||||
// by setting an empty default-column-width.
|
||||
@@ -374,11 +374,19 @@ binds {
|
||||
// Example volume keys mappings for PipeWire & WirePlumber.
|
||||
// The allow-when-locked=true property makes them work even when the session is locked.
|
||||
// Using spawn-sh allows to pass multiple arguments together with the command.
|
||||
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+"; }
|
||||
// "-l 1.0" limits the volume to 100%.
|
||||
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+ -l 1.0"; }
|
||||
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1-"; }
|
||||
XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; }
|
||||
XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; }
|
||||
|
||||
// Example media keys mapping using playerctl.
|
||||
// This will work with any MPRIS-enabled media player.
|
||||
XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; }
|
||||
XF86AudioStop allow-when-locked=true { spawn-sh "playerctl stop"; }
|
||||
XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; }
|
||||
XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; }
|
||||
|
||||
// Example brightness key mappings for brightnessctl.
|
||||
// You can use regular spawn with multiple arguments too (to avoid going through "sh"),
|
||||
// but you need to manually put each argument in separate "" quotes.
|
||||
@@ -542,14 +550,23 @@ binds {
|
||||
// Expel the bottom window from the focused column to the right.
|
||||
Mod+Period { expel-window-from-column; }
|
||||
|
||||
// Cycle through widths set in preset-column-widths.
|
||||
Mod+R { switch-preset-column-width; }
|
||||
// Cycling through the presets in reverse order is also possible.
|
||||
// Mod+R { switch-preset-column-width-back; }
|
||||
Mod+Shift+R { switch-preset-window-height; }
|
||||
Mod+Shift+R { switch-preset-column-width-back; }
|
||||
|
||||
Mod+Ctrl+Shift+R { switch-preset-window-height; }
|
||||
Mod+Ctrl+R { reset-window-height; }
|
||||
|
||||
Mod+F { maximize-column; }
|
||||
Mod+Shift+F { fullscreen-window; }
|
||||
|
||||
// While maximize-column leaves gaps and borders around the window,
|
||||
// maximize-window-to-edges doesn't: the window expands to the edges of the screen.
|
||||
// This bind corresponds to normal window maximizing,
|
||||
// e.g. by double-clicking on the titlebar.
|
||||
Mod+M { maximize-window-to-edges; }
|
||||
|
||||
// Expand the focused column to space not taken up by other fully visible columns.
|
||||
// Makes the column "fill the rest of the space".
|
||||
Mod+Ctrl+F { expand-column-to-available-width; }
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
type = process
|
||||
command = niri --session
|
||||
restart = false
|
||||
working-dir = $HOME
|
||||
depends-on = dbus
|
||||
after = niri-shutdown
|
||||
chain-to = niri-shutdown
|
||||
options: always-chain
|
||||
type = process
|
||||
command = niri --session
|
||||
restart = false
|
||||
working-dir = $HOME
|
||||
ready-notification = pipevar:NOTIFY_FD
|
||||
logfile = $HOME/.local/share/niri/niri.log
|
||||
depends-on: dbus
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
type = scripted
|
||||
command = dinitctl -u setenv WAYLAND_DISPLAY= XDG_SESSION_TYPE= XDG_CURRENT_DESKTOP= NIRI_SOCKET=
|
||||
restart = false
|
||||
@@ -0,0 +1,6 @@
|
||||
type = internal
|
||||
restart = false
|
||||
depends-on: niri
|
||||
waits-for.d: $XDG_CONFIG_HOME/dinit.d/niri.d/
|
||||
waits-for.d: $HOME/.config/dinit.d/niri.d/
|
||||
waits-for.d: /etc/dinit.d/user/niri.d/
|
||||
+26
-2
@@ -59,13 +59,37 @@ elif hash dinitctl >/dev/null 2>&1; then
|
||||
fi
|
||||
|
||||
# Make sure there's no already running session.
|
||||
if dinitctl --user is-started niri >/dev/null 2>&1; then
|
||||
if dinitctl --quiet --user is-started niri 2>/dev/null; then
|
||||
echo 'A niri session is already running.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Import the login manager environment into dinit
|
||||
# Might not work correctly for multiline variable names, but
|
||||
# it is reasonable to assume there are none
|
||||
awk 'BEGIN{for(v in ENVIRON) if (v != "AWKPATH" && v != "AWKLIBPATH") print v}' 2>/dev/null | xargs dinitctl --quiet --user setenv 2>/dev/null
|
||||
|
||||
# Usually the dbus service would start as niri's dependency and inherit
|
||||
# environment from dinit, but in case it has already started we need
|
||||
# to update its environment.
|
||||
if hash dbus-update-activation-environment >/dev/null 2>&1; then
|
||||
dbus-update-activation-environment --all >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Create the directory for the logfile, if doesn't exist
|
||||
mkdir --parents $HOME/.local/share/niri
|
||||
# Start niri
|
||||
dinitctl --user start niri
|
||||
dinitctl --quiet --user start niri.target 2>&1
|
||||
|
||||
# Wait for termination
|
||||
dinit-monitor --user --initial -c $'sh -c "
|
||||
if [ "%s" = "stopped" ] || [ "%s" = "failed" ]; then
|
||||
ppid=$(ps -o ppid= -p $$)
|
||||
kill $ppid
|
||||
fi"' niri >/dev/null 2>&1
|
||||
|
||||
# Unset environment that we've set.
|
||||
dinitctl --quiet --user unsetenv WAYLAND_DISPLAY DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP NIRI_SOCKET 2>/dev/null
|
||||
else
|
||||
echo "No systemd or dinit detected, please use niri --session instead."
|
||||
fi
|
||||
|
||||
@@ -11,4 +11,4 @@ Before=xdg-desktop-autostart.target
|
||||
[Service]
|
||||
Slice=session.slice
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/niri --session
|
||||
ExecStart=niri --session
|
||||
|
||||
+78
-1
@@ -7,20 +7,27 @@ use accesskit::{
|
||||
};
|
||||
use accesskit_unix::Adapter;
|
||||
use calloop::LoopHandle;
|
||||
use niri_config::MruScope;
|
||||
|
||||
use crate::layout::workspace::WorkspaceId;
|
||||
use crate::niri::{KeyboardFocus, Niri, State};
|
||||
use crate::utils::with_toplevel_role;
|
||||
use crate::window::mapped::MappedId;
|
||||
|
||||
const ID_ROOT: NodeId = NodeId(0);
|
||||
const ID_ANNOUNCEMENT: NodeId = NodeId(1);
|
||||
const ID_SCREENSHOT_UI: NodeId = NodeId(2);
|
||||
const ID_EXIT_CONFIRM_DIALOG: NodeId = NodeId(3);
|
||||
const ID_OVERVIEW: NodeId = NodeId(4);
|
||||
const ID_MRU: NodeId = NodeId(5);
|
||||
|
||||
pub struct A11y {
|
||||
event_loop: LoopHandle<'static, State>,
|
||||
focus: NodeId,
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
mru_selection: Option<MappedId>,
|
||||
mru_scope: Option<MruScope>,
|
||||
last_mru_title: String,
|
||||
last_announcement: String,
|
||||
to_accesskit: Option<mpsc::SyncSender<TreeUpdate>>,
|
||||
}
|
||||
@@ -37,6 +44,9 @@ impl A11y {
|
||||
event_loop,
|
||||
focus: ID_ROOT,
|
||||
workspace_id: None,
|
||||
mru_selection: None,
|
||||
mru_scope: None,
|
||||
last_mru_title: String::new(),
|
||||
last_announcement: String::new(),
|
||||
to_accesskit: None,
|
||||
}
|
||||
@@ -128,9 +138,30 @@ impl Niri {
|
||||
self.a11y.workspace_id = ws_id;
|
||||
|
||||
let focus = self.a11y_focus();
|
||||
|
||||
// Check if the MRU selection changed.
|
||||
let mut update_mru_selection = false;
|
||||
if focus == ID_MRU {
|
||||
let current = self.window_mru_ui.current_window_id();
|
||||
if self.a11y.mru_selection != current {
|
||||
update_mru_selection = true;
|
||||
self.a11y.mru_selection = current;
|
||||
}
|
||||
|
||||
// If there's no window title to announce, check if there's a scope change.
|
||||
let scope = self.window_mru_ui.scope();
|
||||
if !update_mru_selection && self.a11y.mru_scope != Some(scope) {
|
||||
announcement = Some(self.window_mru_ui.a11y_scope_text());
|
||||
}
|
||||
self.a11y.mru_scope = Some(scope);
|
||||
} else {
|
||||
self.a11y.mru_scope = None;
|
||||
self.a11y.mru_selection = None;
|
||||
}
|
||||
|
||||
let update_focus = self.a11y.focus != focus;
|
||||
|
||||
if !(announcement.is_some() || update_focus) {
|
||||
if !(announcement.is_some() || update_focus || update_mru_selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,6 +180,43 @@ impl Niri {
|
||||
nodes.push((ID_ANNOUNCEMENT, node));
|
||||
}
|
||||
|
||||
if focus == ID_MRU {
|
||||
// Ideally MRU would be a Group with a child Button for a window, but I've no idea how
|
||||
// to make it work reliably. When I did it that way, there were two issues:
|
||||
//
|
||||
// 1. Alt-Tab would always start reading from "Recent windows grouping" instead of the
|
||||
// window title.
|
||||
// 2. When Alt-Tab became empty (e.g. switching scope to something empty), Orca would
|
||||
// completely stop reading any child buttons for the remainder of the session.
|
||||
//
|
||||
// I've no idea what to do about these and where they even come from. So, just flip the
|
||||
// MRU node between Group and Button, which seems to work fine.
|
||||
if update_mru_selection {
|
||||
if let Some(id) = self.a11y.mru_selection {
|
||||
if let Some((_, mapped)) = self.layout.windows().find(|(_, m)| m.id() == id) {
|
||||
with_toplevel_role(mapped.toplevel(), |role| {
|
||||
let mut title = role.title.as_deref().unwrap_or("Unknown").to_owned();
|
||||
// Change title on match to ensure we announce same-titled windows.
|
||||
if self.a11y.last_mru_title == title {
|
||||
title.push(' ');
|
||||
}
|
||||
self.a11y.last_mru_title = title;
|
||||
|
||||
let mut mru = Node::new(Role::Button);
|
||||
mru.set_label(&*self.a11y.last_mru_title);
|
||||
nodes.push((ID_MRU, mru));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let mut mru = Node::new(Role::Group);
|
||||
// Announce the current scope in the empty text to make it clear.
|
||||
let scope = self.window_mru_ui.a11y_scope_text();
|
||||
mru.set_label(format!("Recent windows empty, {scope}"));
|
||||
nodes.push((ID_MRU, mru));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let update = TreeUpdate {
|
||||
nodes,
|
||||
tree: None,
|
||||
@@ -205,6 +273,7 @@ impl Niri {
|
||||
KeyboardFocus::ScreenshotUi => ID_SCREENSHOT_UI,
|
||||
KeyboardFocus::ExitConfirmDialog => ID_EXIT_CONFIRM_DIALOG,
|
||||
KeyboardFocus::Overview => ID_OVERVIEW,
|
||||
KeyboardFocus::Mru => ID_MRU,
|
||||
_ => ID_ROOT,
|
||||
}
|
||||
}
|
||||
@@ -237,12 +306,16 @@ impl Niri {
|
||||
let mut overview = Node::new(Role::Group);
|
||||
overview.set_label("Overview");
|
||||
|
||||
let mut mru = Node::new(Role::Group);
|
||||
mru.set_label("Recent windows");
|
||||
|
||||
let mut root = Node::new(Role::Window);
|
||||
root.set_children(vec![
|
||||
ID_ANNOUNCEMENT,
|
||||
ID_SCREENSHOT_UI,
|
||||
ID_EXIT_CONFIRM_DIALOG,
|
||||
ID_OVERVIEW,
|
||||
ID_MRU,
|
||||
]);
|
||||
|
||||
let tree = Tree {
|
||||
@@ -253,6 +326,9 @@ impl Niri {
|
||||
|
||||
let focus = self.a11y_focus();
|
||||
|
||||
// NOTE: we don't fill in current MRU selection here to avoid duplicating code; it should
|
||||
// get updated right away anyway.
|
||||
|
||||
TreeUpdate {
|
||||
nodes: vec![
|
||||
(ID_ROOT, root),
|
||||
@@ -260,6 +336,7 @@ impl Niri {
|
||||
(ID_SCREENSHOT_UI, screenshot_ui),
|
||||
(ID_EXIT_CONFIRM_DIALOG, exit_confirm_dialog),
|
||||
(ID_OVERVIEW, overview),
|
||||
(ID_MRU, mru),
|
||||
],
|
||||
tree: Some(tree),
|
||||
focus,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user