mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Compare commits
492 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66d6355b25 | |||
| 56654034e9 | |||
| 1f07cffa9f | |||
| cb3a06cd54 | |||
| f115f2e5e7 | |||
| 2e07282977 | |||
| cba0454c94 | |||
| 5f6f131b24 | |||
| adb5b3cd2c | |||
| 0650e7b640 | |||
| dd1c3bcb9f | |||
| e5d463e15b | |||
| 26100096e8 | |||
| a85b922919 | |||
| 7d2b620ce9 | |||
| a48f2645d9 | |||
| 83e839762f | |||
| 4c1196f45b | |||
| 8ed0da44d9 | |||
| 91be662ac6 | |||
| 4438aefc8d | |||
| 6c4dfd7772 | |||
| 1ad422f0db | |||
| 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 | |||
| 01be0e65f4 | |||
| 96d9e5e956 | |||
| 5dd6fbddb5 | |||
| a62b38790e | |||
| af4b5f99e9 | |||
| ddb9dd407d | |||
| 0cc041f5ad | |||
| 6e0b38050d | |||
| bf472970c6 | |||
| 082d0581b7 | |||
| fd6dd826fb | |||
| 2bb26f0281 | |||
| e11c35c387 | |||
| 90ef2020c4 | |||
| dfe463ed7d | |||
| 0c3223ac72 | |||
| 1ffda91e0c | |||
| d9833fc1c3 | |||
| dfcd5e9497 | |||
| 5b26f58285 | |||
| 7115b214b2 | |||
| b02c5d2d36 | |||
| e5188da1f8 | |||
| 82697773f8 | |||
| f64cb6c03e | |||
| 91257862e5 | |||
| 178469301b | |||
| c637d4ce99 | |||
| a03a7bd1a3 | |||
| 758a4c5e65 | |||
| 66584ab466 | |||
| 3c11004515 | |||
| 02e3f9f66e | |||
| 19c576cfd8 | |||
| ab52bb9521 | |||
| da5e33775c | |||
| 1e3ab8a880 | |||
| efa2ae2d3f | |||
| e038b8770a | |||
| f6f4bf97c3 | |||
| 7cbb6288c3 | |||
| 39e5f637ef | |||
| effae2bc2b | |||
| 43638df235 | |||
| 4ba79d94ac | |||
| d4dad2939d | |||
| 1f76dce345 | |||
| e1afa71238 | |||
| 9b622b1c8c | |||
| 8a2d408cb6 | |||
| 8b73910a11 | |||
| 969f382e3e | |||
| 2865ec3e47 | |||
| b3c7737998 | |||
| 58290516c7 | |||
| 210d5e90fe | |||
| 9d3beb4931 | |||
| 7aba44a019 | |||
| d662811bf6 | |||
| 05337ce855 | |||
| c9e85a0fe2 | |||
| 0a8b4e036d | |||
| 70f9ac4af8 |
@@ -1,12 +1,2 @@
|
||||
# LFS configuration for images from the wiki
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Exclude LFS-tracked files from the tarball
|
||||
/wiki/img/ export-ignore
|
||||
|
||||
# exclude .gitattributes itself from the tarball
|
||||
.gitattributes export-ignore
|
||||
|
||||
# tip: can be tested using
|
||||
# git archive --format=tar.gz --output=source.tar.gz HEAD && \
|
||||
# tar tfvz source.tar.gz | grep -e '.png' -e '.gitattributes'
|
||||
|
||||
@@ -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
+1096
-1239
File diff suppressed because it is too large
Load Diff
+60
-48
@@ -6,34 +6,35 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "25.5.1"
|
||||
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.2"
|
||||
clap = { version = "4.5.45", 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,49 +51,53 @@ readme = "README.md"
|
||||
keywords = ["wayland", "compositor", "tiling", "smithay", "wm"]
|
||||
|
||||
[dependencies]
|
||||
# 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.5.1", path = "niri-config" }
|
||||
niri-ipc = { version = "25.5.1", 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.4", 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.9.0", optional = true }
|
||||
zbus = { version = "5.13.2", optional = true }
|
||||
|
||||
[dependencies.smithay]
|
||||
workspace = true
|
||||
@@ -114,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, power button handling).
|
||||
dbus = ["dep:zbus", "dep:async-io", "dep:url"]
|
||||
# Enables D-Bus support (serve various freedesktop and GNOME interfaces, accessibility tree, power button handling).
|
||||
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.
|
||||
@@ -139,6 +147,10 @@ profile-with-tracy-allocations = ["profile-with-tracy"]
|
||||
# Enables dinit integration (global environment).
|
||||
dinit = []
|
||||
|
||||
[lints.clippy]
|
||||
new_without_default = "allow"
|
||||
collapsible_match = "allow"
|
||||
|
||||
[profile.release]
|
||||
debug = "line-tables-only"
|
||||
overflow-checks = true
|
||||
@@ -153,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" },
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<h1 align="center">niri</h1>
|
||||
<h1 align="center"><img alt="niri" src="https://github.com/user-attachments/assets/07d05cd0-d5dc-4a28-9a35-51bae8f119a0"></h1>
|
||||
<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>
|
||||
|
||||

|
||||
<img width="1280" height="720" alt="niri with a few windows open" src="https://github.com/user-attachments/assets/dea5909e-1859-4aaa-9d88-d37f9663e00b" />
|
||||
|
||||
## About
|
||||
|
||||
@@ -29,24 +29,29 @@ 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
|
||||
- [Background blur](https://niri-wm.github.io/niri/Window-Effects.html) for windows and layer-shell surfaces
|
||||
- [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://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)
|
||||
Also check out these videos that showcase a lot of the niri functionality:
|
||||
|
||||
- [Niri Is My New Favorite Wayland Compositor](https://www.youtube.com/watch?v=DeYx2exm04M) by Brodie Robertson
|
||||
- [How Is niri This Good? Live Demo + Config](https://www.youtube.com/watch?v=7XmD5UyyhZQ) by Nick Janetakis
|
||||
|
||||
## Status
|
||||
|
||||
@@ -54,8 +59,8 @@ 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.
|
||||
Have your [waybar]s and [fuzzel]s ready: niri is not a complete desktop environment.
|
||||
Follow the instructions on the [Getting Started](https://niri-wm.github.io/niri/Getting-Started.html) page.
|
||||
Grab a desktop shell like [DankMaterialShell] or [Noctalia] (or build a more traditional setup): niri by itself is not a complete desktop environment.
|
||||
Also check out [awesome-niri], a list of niri-related links and projects.
|
||||
|
||||
Here are some points you may have questions about:
|
||||
@@ -71,14 +76,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**: no built-in support, but xwayland-satellite is [easy to set up](https://yalter.github.io/niri/Xwayland.html#using-xwayland-satellite) and works very well.
|
||||
- Steam and games, including Proton: work perfectly through xwayland-satellite.
|
||||
- JetBrains IDEs, Ghidra: work well through xwayland-satellite.
|
||||
- Discord and other Electron apps: work well through xwayland-satellite.
|
||||
- Chromium and VSCode: work perfectly natively on Wayland with the right flags.
|
||||
- X11 apps that want to position windows or bars at specific screen coordinates: won't work well; you can run them in a nested compositor like [labwc](https://yalter.github.io/niri/Xwayland.html#using-the-labwc-wayland-compositor) or [rootful Xwayland](https://yalter.github.io/niri/Xwayland.html#directly-running-xwayland-in-rootful-mode).
|
||||
- Display scaling (integer or fractional) keeps X11 apps crisp, but you need the latest xwayland-satellite.
|
||||
For games, you can run them in [gamescope] at native resolution, even with display scaling.
|
||||
- **Xwayland**: [integrated](https://niri-wm.github.io/niri/Xwayland.html#using-xwayland-satellite) via xwayland-satellite starting from niri 25.08.
|
||||
|
||||
## Media
|
||||
|
||||
@@ -99,7 +97,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
|
||||
|
||||
@@ -115,8 +113,8 @@ Here are some other projects which implement a similar workflow:
|
||||
- [PaperWM]: scrollable tiling on top of GNOME Shell.
|
||||
- [karousel]: scrollable tiling on top of KDE.
|
||||
- [scroll](https://github.com/dawsers/scroll) and [papersway]: scrollable tiling on top of sway/i3.
|
||||
- [hyprscrolling] and [hyprslidr]: scrollable tiling on top of Hyprland.
|
||||
- [PaperWM.spoon]: scrollable tiling on top of macOS.
|
||||
- Hyprland has a built-in [scrolling layout](https://wiki.hypr.land/Configuring/Layouts/Scrolling-Layout/).
|
||||
- [Paneru] and [PaperWM.spoon]: scrollable tiling on top of macOS.
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -127,12 +125,12 @@ 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
|
||||
[hyprslidr]: https://gitlab.com/magus/hyprslidr
|
||||
[Paneru]: https://github.com/karinushka/paneru
|
||||
[PaperWM.spoon]: https://github.com/mogenson/PaperWM.spoon
|
||||
[Matrix channel]: https://matrix.to/#/#niri:matrix.org
|
||||
[OpenTabletDriver]: https://opentabletdriver.net/
|
||||
[gamescope]: https://github.com/ValveSoftware/gamescope
|
||||
[DankMaterialShell]: https://danklinux.com/
|
||||
[Noctalia]: https://noctalia.dev/
|
||||
|
||||
@@ -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>"
|
||||
|
||||
+11
-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,7 +84,13 @@ 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
|
||||
- Security Model: Security-Model.md
|
||||
- Accessibility: Accessibility.md
|
||||
- Name and Logo: Name-and-Logo.md
|
||||
- FAQ: FAQ.md
|
||||
- Configuration:
|
||||
- Introduction: Configuration:-Introduction.md
|
||||
@@ -99,11 +105,14 @@ 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
|
||||
- Documenting niri: Development:-Documenting-niri.md
|
||||
- Releasing niri: Development:-Releasing-niri.md
|
||||
- Fractional Layout: Development:-Fractional-Layout.md
|
||||
- Redraw Loop: Development:-Redraw-Loop.md
|
||||
- Animation Timing: Development:-Animation-Timing.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" }
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
## Screen readers
|
||||
|
||||
<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. 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" 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).
|
||||
|
||||
Here's a demo video, watch with sound on.
|
||||
|
||||
<video controls src="https://github.com/user-attachments/assets/afceba6f-79f1-47ec-b859-a0fcb7f8eae3">
|
||||
|
||||
https://github.com/user-attachments/assets/afceba6f-79f1-47ec-b859-a0fcb7f8eae3
|
||||
|
||||
</video>
|
||||
|
||||
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 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. 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.
|
||||
|
||||
## Desktop zoom
|
||||
|
||||
There's no built-in zoom yet, but you can use third-party utilities like [wooz](https://github.com/negrel/wooz).
|
||||
@@ -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.
|
||||
@@ -93,3 +113,12 @@ window-rule {
|
||||
default-floating-position x=10 y=10 relative-to="bottom-right"
|
||||
}
|
||||
```
|
||||
|
||||
### Waybar and other GTK 3 components
|
||||
|
||||
If you have rounded corners on your Waybar and they show up with black pixels in the corners, then set your Waybar opacity to 0.99, which should fix it.
|
||||
|
||||
GTK 3 seems to have a bug where it reports a surface as fully opaque even if it has rounded corners.
|
||||
This leads to niri filling the transparent pixels inside the corners with black.
|
||||
|
||||
Setting the surface opacity to something below 1 fixes the problem because then GTK no longer reports the surface as opaque.
|
||||
|
||||
@@ -46,6 +46,10 @@ animations {
|
||||
spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
|
||||
}
|
||||
|
||||
exit-confirmation-open-close {
|
||||
spring damping-ratio=0.6 stiffness=500 epsilon=0.01
|
||||
}
|
||||
|
||||
screenshot-ui-open {
|
||||
duration-ms 200
|
||||
curve "ease-out-quad"
|
||||
@@ -54,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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -80,14 +88,24 @@ animations {
|
||||
}
|
||||
```
|
||||
|
||||
Currently, niri only supports four curves:
|
||||
Currently, niri only supports five curves.
|
||||
You can get a feel for them on pages like [easings.net](https://easings.net/).
|
||||
|
||||
- `ease-out-quad` <sup>Since: 0.1.5</sup>
|
||||
- `ease-out-cubic`
|
||||
- `ease-out-expo`
|
||||
- `linear` <sup>Since: 0.1.6</sup>
|
||||
|
||||
You can get a feel for them on pages like [easings.net](https://easings.net/).
|
||||
- `cubic-bezier` <sup>Since: 25.08</sup>
|
||||
A custom [cubic Bézier curve](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions). You need to set 4 numbers defining the control points of the curve, for example:
|
||||
```kdl
|
||||
animations {
|
||||
window-open {
|
||||
// Same as CSS cubic-bezier(0.05, 0.7, 0.1, 1)
|
||||
curve "cubic-bezier" 0.05 0.7 0.1 1
|
||||
}
|
||||
}
|
||||
```
|
||||
You can tweak the cubic-bezier parameters on pages like [easings.co](https://easings.co?curve=0.05,0.7,0.1,1).
|
||||
|
||||
#### Spring
|
||||
|
||||
@@ -363,6 +381,22 @@ animations {
|
||||
}
|
||||
```
|
||||
|
||||
#### `exit-confirmation-open-close`
|
||||
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
The open/close animation of the exit confirmation dialog.
|
||||
|
||||
This one uses an underdamped spring by default (`damping-ratio=0.6`) which causes a slight oscillation in the end.
|
||||
|
||||
```kdl
|
||||
animations {
|
||||
exit-confirmation-open-close {
|
||||
spring damping-ratio=0.6 stiffness=500 epsilon=0.01
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `screenshot-ui-open`
|
||||
|
||||
<sup>Since: 0.1.8</sup>
|
||||
@@ -392,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>
|
||||
@@ -263,7 +294,7 @@ debug {
|
||||
|
||||
### `skip-cursor-only-updates-during-vrr`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Skips redrawing the screen from cursor input while variable refresh rate is active.
|
||||
|
||||
@@ -279,7 +310,7 @@ debug {
|
||||
|
||||
### `deactivate-unfocused-windows`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Some clients (notably, Chromium- and Electron-based, like Teams or Slack) erroneously use the Activated xdg window state instead of keyboard focus for things like deciding whether to send notifications for new messages, or for picking where to show an IME popup.
|
||||
Niri keeps the Activated state on unfocused workspaces and invisible tabbed windows (to reduce unwanted animations), surfacing bugs in these applications.
|
||||
@@ -295,7 +326,7 @@ debug {
|
||||
|
||||
### `keep-max-bpc-unchanged`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
When connecting monitors, niri sets their max bpc to 8 in order to reduce display bandwidth and to potentially allow more monitors to be connected at once.
|
||||
Restricting bpc to 8 is not a problem since we don't support HDR or color management yet and can't really make use of higher bpc.
|
||||
@@ -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,8 @@ input {
|
||||
tablet {
|
||||
// off
|
||||
map-to-output "eDP-1"
|
||||
// map-to-focused-output
|
||||
// map-to-focused-window
|
||||
// left-handed
|
||||
// calibration-matrix 1.0 0.0 0.0 0.0 1.0 0.0
|
||||
}
|
||||
@@ -96,6 +98,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
|
||||
@@ -147,7 +150,7 @@ input {
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> <sup>Since: next release</sup>
|
||||
> <sup>Since: 25.08</sup>
|
||||
>
|
||||
> If the `xkb` section is empty (like it is by default), niri will fetch xkb settings from systemd-localed at `org.freedesktop.locale1` over D-Bus.
|
||||
> This way, for example, system installers can dynamically set the niri keyboard layout.
|
||||
@@ -235,7 +238,7 @@ A few settings are common between `touchpad`, `mouse`, `trackpoint`, and `trackb
|
||||
- `scroll-method`: when to generate scroll events instead of pointer motion events, can be `no-scroll`, `two-finger`, `edge`, or `on-button-down`.
|
||||
The default and supported methods vary depending on the device type.
|
||||
- `scroll-button`: <sup>Since: 0.1.10</sup> the button code used for the `on-button-down` scroll method. You can find it in `libinput debug-events`.
|
||||
- `scroll-button-lock`: <sup>Since: next release</sup> when enabled, the button does not need to be held down. Pressing once engages scrolling, pressing a second time disengages it, and double click acts as single click of the the underlying button.
|
||||
- `scroll-button-lock`: <sup>Since: 25.08</sup> when enabled, the button does not need to be held down. Pressing once engages scrolling, pressing a second time disengages it, and double click acts as single click of the the underlying button.
|
||||
- `left-handed`: if set, changes the device to left-handed mode.
|
||||
- `middle-emulation`: emulate a middle mouse click by pressing left and right mouse buttons at once.
|
||||
|
||||
@@ -254,11 +257,13 @@ Settings specific to `touchpad` and `mouse`:
|
||||
|
||||
- `scroll-factor`: <sup>Since: 0.1.10</sup> scales the scrolling speed by this value.
|
||||
|
||||
<sup>Since: next release</sup> You can also override horizontal and vertical scroll factor separately like so: `scroll-factor horizontal=2.0 vertical=-1.0`
|
||||
<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 +283,16 @@ 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.
|
||||
|
||||
Settings 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`.
|
||||
|
||||
- `map-to-focused-window`: <sup>Since: next release</sup> will map the tablet to the focused window's geometry, takes precedence over `map-to-focused-output` and `map-to-output`.
|
||||
Falls back to those when no window is focused (for example, in the overview).
|
||||
|
||||
When the tablet is also mapped to a specific output via `map-to-output`, the `map-to-focused-window` flag will map the tablet to the active window on that output.
|
||||
If the tablet isn't mapped to any specific output, it will map the tablet to the current focused window regardless of where it is.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -252,7 +252,7 @@ binds {
|
||||
|
||||
#### `spawn-sh`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Run a command through the shell.
|
||||
|
||||
@@ -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`
|
||||
@@ -74,7 +82,7 @@ Thanks to this, apps that you configured to autostart in GNOME will also "just w
|
||||
|
||||
### `spawn-sh-at-startup`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Add lines like this to run shell commands at niri 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.
|
||||
@@ -235,7 +250,7 @@ overview {
|
||||
|
||||
### `xwayland-satellite`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Settings for integration with [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite).
|
||||
|
||||
@@ -286,7 +301,7 @@ hotkey-overlay {
|
||||
|
||||
#### `hide-not-bound`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
By default, niri will show the most important actions even if they aren't bound to any key, to prevent confusion.
|
||||
Set the `hide-not-bound` flag if you want to hide all actions not bound to any key.
|
||||
@@ -301,7 +316,7 @@ You can customize which binds the hotkey overlay shows using the [`hotkey-overla
|
||||
|
||||
### `config-notification`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
Settings for the config created/failed notification.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
> *Time, Dr. Freeman? Is it really that... time again?*
|
||||
|
||||
A compositor deals with one or more monitors on mostly fixed refresh cycles.
|
||||
For example, a 170 Hz monitor can draw a frame every ~5.88 ms.
|
||||
For example, a 170 Hz monitor can draw a frame every ~5.88 ms.
|
||||
|
||||
Most of the time, the compositor doesn't actually redraw the monitor: when nothing changes on screen (e.g. you're reading a document and aren't moving your cursor), it would be wasteful to wake up the GPU to composite the same image.
|
||||
During an animation however, screen contents do change every frame.
|
||||
|
||||
@@ -1,30 +1,113 @@
|
||||
These are some of the general principles for the design of niri's window layout.
|
||||
## General principles
|
||||
|
||||
These are some of the general principles that I try to follow throughout niri.
|
||||
They can be sidestepped in specific circumstances if there's a good reason.
|
||||
|
||||
1. Opening a new window should not affect the sizes of any existing windows.
|
||||
1. The focused window should not move around on its own.
|
||||
- In particular: windows opening, closing, and resizing to the left of the focused window should not cause it to visually move.
|
||||
1. Actions should apply immediately.
|
||||
- Things like resizing or consuming into column take effect immediately, even if the window needs time to catch up.
|
||||
- This is important both for compositor responsiveness and predictability, and for keeping the code sane and free of edge cases and unnecessary asynchrony.
|
||||
### Opening a new window should not affect the sizes of any existing windows.
|
||||
|
||||
This is the main annoyance with traditional tiling: you want to open a new window, but it messes with your existing window sizes.
|
||||
Especially when you're looking at a big window like a browser or an image editor, want to open a quick terminal for something, and it makes the big window unusably small, or reflows the content, or clips part of the window.
|
||||
|
||||
The usual workaround in tiling WMs is to use more workspaces: when you need a new window, you go to an empty workspace and open it there (this way, you also get your entire screen for the new window, rather than a smaller part of it).
|
||||
|
||||
Scrollable tiling offers an alternative: for temporary windows, you can just open them, do what you need, and close, all without messing up the other windows or having to go to a new workspace.
|
||||
It also lets you group together more related windows on the same workspace by having less frequently used ones scrolled out of the view.
|
||||
|
||||
### The focused window should not move around on its own.
|
||||
|
||||
In particular: windows opening, closing, and resizing to the left of the focused window should not cause it to visually move.
|
||||
|
||||
The focused window is the window you're working in.
|
||||
And stuff happening outside the view shouldn't mess with what you're focused on.
|
||||
|
||||
### Actions should apply immediately.
|
||||
|
||||
This is important both for compositor responsiveness and predictability, and for keeping the code sane and free of edge cases and unnecessary asynchrony.
|
||||
|
||||
- Things like resizing or consuming into column take effect immediately, even if the window needs time to catch up.
|
||||
- An animated workspace switch makes your input go to the final workspace and window instantly, without waiting for the animation.
|
||||
- Opening the overview (which has a zoom-out animation) lets you grab windows right away, and closing the overview makes your input immediately go back to the windows, without waiting for the zoom back in.
|
||||
|
||||
### When disabled, eye-candy features should not affect the performance.
|
||||
|
||||
Things like animations and custom shaders do not run and are not present in the render tree when disabled.
|
||||
Extra offscreen rendering is avoided.
|
||||
|
||||
Animations specifically are still "started" even when disabled, but with a duration of 0 (this way, they end as soon as the time is advanced).
|
||||
This does not impact performance, but helps avoid a lot of edge cases in the code.
|
||||
|
||||
### Eye-candy features should not cause unreasonable excessive rendering.
|
||||
|
||||
- For example, clip-to-geometry will prevent direct scanout in many cases (since the window surface is not completely visible). But in the cases where the surface or the subsurface *is* completely visible (fully within the clipped region), it will still allow for direct scanout.
|
||||
- For example, animations *can* cause damage and even draw to an offscreen every frame, because they are expected to be short (and can be disabled). However, something like the rounded corners shader should not offscreen or cause excessive damage every frame, because it is long-running and constantly active.
|
||||
|
||||
### Be mindful of invisible state.
|
||||
|
||||
This is niri state that is not immediately apparent from looking at the screen. This is not bad per se, but you should carefully consider how to reduce the surprise factor.
|
||||
|
||||
- For example, when a monitor disconnects, all its workspaces move to another connected monitor. In order to be able to restore these workspaces when the first monitor connects again, these workspaces keep the knowledge of which was their *original monitor*—this is an example of invisible state, since you can't tell it in any way by looking at the screen. This can have surprising consequences: imagine disconnecting a monitor at home, going to work, completely rearranging the windows there, then coming back home, and suddenly some random workspaces end up on your home monitor. In order to reduce this surprise factor, whenever a new window appears on a workspace, that workspace resets its *original monitor* to its current monitor. This way, the workspaces you actively worked on remain where they were.
|
||||
- For example, niri preserves the view position whenever a window appears, or whenever a window goes full-screen, to restore it afterward. This way, dealing with temporary things like dialogs opening and closing, or toggling full-screen, becomes less annoying, since it doesn't mess up the view position. This is also invisible state, as you cannot tell by looking at the screen where closing a window will restore the view position. If taken to the extreme (previous view position saved forever for every open window), this can be surprising, as closing long-running windows would result in the view shifting around pretty much randomly. To reduce this surprise factor, niri remembers only one last view position per workspace, and forgets this stored view position upon window focus change.
|
||||
|
||||
## Window layout
|
||||
|
||||
Here are some design considerations for the window layout logic.
|
||||
|
||||
1. If a window or popup is larger than the screen, it should be aligned in the top left corner.
|
||||
- The top left area of a window is more likely to contain something important, so it should always be visible.
|
||||
|
||||
The top left area of a window is more likely to contain something important, so it should always be visible.
|
||||
|
||||
1. Setting window width or height to a fixed pixel size (e.g. `set-column-width 1280` or `default-column-width { fixed 1280; }`) will set the size of the window itself, however setting to a proportional size (e.g. `set-column-width 50%`) will set the size of the tile, including the border added by niri.
|
||||
|
||||
- With proportions, the user is looking to tile multiple windows on the screen, so they should include borders.
|
||||
- With fixed sizes, the user wants to test a specific client size or take a specifically sized screenshot, so they should affect the window directly.
|
||||
- After the size is set, it is always converted to a value that includes the borders, to make the code sane. That is, `set-column-width 1000` followed by changing the niri border width will resize the window accordingly.
|
||||
|
||||
And here are some more principles I try to follow throughout niri.
|
||||
1. Fullscreen windows are a normal part of the scrolling layout.
|
||||
|
||||
1. When disabled, eye-candy features should not affect the performance.
|
||||
- Things like animations and custom shaders do not run and are not present in the render tree when disabled. Extra offscreen rendering is avoided.
|
||||
- Animations specifically are still "started" even when disabled, but with a duration of 0 (this way, they end as soon as the time is advanced). This does not impact performance, but helps avoid a lot of edge cases in the code.
|
||||
1. Eye-candy features should not cause unreasonable excessive rendering.
|
||||
- For example, clip-to-geometry will prevent direct scanout in many cases (since the window surface is not completely visible). But in the cases where the surface or the subsurface *is* completely visible (fully within the clipped region), it will still allow for direct scanout.
|
||||
- For example, animations *can* cause damage and even draw to an offscreen every frame, because they are expected to be short (and can be disabled). However, something like the rounded corners shader should not offscreen or cause excessive damage every frame, because it is long-running and constantly active.
|
||||
1. Be mindful of invisible state.
|
||||
This is a cool idea that scrollable tiling is uniquely positioned to implement.
|
||||
Fullscreen windows aren't on some "special" layer that covers everything; instead, they are normal tiles that you can switch away from, without disturbing the fullscreen status.
|
||||
|
||||
This is niri state that is not immediately apparent from looking at the screen. This is not bad per se, but you should carefully consider how to reduce the surprise factor.
|
||||
Of course, you do want to cover your entire monitor when focused on a fullscreen window.
|
||||
This is specifically hardcoded into the logic: when the view is stationary on a focused fullscreen window, the top layer-shell layer and the floating windows hide away.
|
||||
|
||||
- For example, when a monitor disconnects, all its workspaces move to another connected monitor. In order to be able to restore these workspaces when the first monitor connects again, these workspaces keep the knowledge of which was their *original monitor*—this is an example of invisible state, since you can't tell it in any way by looking at the screen. This can have surprising consequences: imagine disconnecting a monitor at home, going to work, completely rearranging the windows there, then coming back home, and suddenly some random workspaces end up on your home monitor. In order to reduce this surprise factor, whenever a new window appears on a workspace, that workspace resets its *original monitor* to its current monitor. This way, the workspaces you actively worked on remain where they were.
|
||||
- For example, niri preserves the view position whenever a window appears, or whenever a window goes full-screen, to restore it afterward. This way, dealing with temporary things like dialogs opening and closing, or toggling full-screen, becomes less annoying, since it doesn't mess up the view position. This is also invisible state, as you cannot tell by looking at the screen where closing a window will restore the view position. If taken to the extreme (previous view position saved forever for every open window), this can be surprising, as closing long-running windows would result in the view shifting around pretty much randomly. To reduce this surprise factor, niri remembers only one last view position per workspace, and forgets this stored view position upon window focus change.
|
||||
This is also why fullscreening a floating window makes it go into the scrolling layout.
|
||||
|
||||
## Default config
|
||||
|
||||
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.
|
||||
As such, new niri users are expected to read through and tinker with the default niri config.
|
||||
|
||||
The default config is therefore thoroughly commented with links to the relevant wiki sections.
|
||||
We don't include every possible option in the default config to avoid overwhelming users too much; anything overly specific or uncommon can stay on the wiki.
|
||||
The general rule is to include things that users are reasonably expected to want to change or know how to do.
|
||||
We do also advertise our more unique features though like screencast block-out-from.
|
||||
|
||||
We default to CSD (`prefer-no-csd` is commented out).
|
||||
This gives new users easy and familiar way to move and close windows via their titlebars, especially considering that niri doesn't have serverside titlebars (so far at least).
|
||||
|
||||
Focus rings are drawn fully behind windows by default.
|
||||
While this unfortunately messes with window transparency, [which is a common source of confusion](./FAQ.md#why-are-transparent-windows-tinted-why-is-the-borderfocus-ring-showing-up-through-semitransparent-windows), defaulting to drawing focus rings only around windows would be even worse because it has holes inside clientside rounded corners.
|
||||
The ideal solution here would be to propose a Wayland protocol for windows to report their corner radius to the compositor (which would generally help for serverside decorations in different compositors).
|
||||
|
||||
The default focus ring is quite thick at 4 px to look well with clientside-decorated windows and be obviously noticeable, and the default gaps are also quite big at 16 px to look well with the default focus ring width.
|
||||
|
||||
The default input settings like touchpad tap and natural-scroll are how I believe most people want to use their computers.
|
||||
|
||||
Shadows default to off because they are a fairly performance-intensive shader, and because many clientside-decorated windows already draw their own shadows.
|
||||
|
||||
The default screenshot-path matches GNOME Shell.
|
||||
|
||||
Default window rules are limited to fixing known severe issues (WezTerm) and doing something the absolute majority likely wants (make Firefox Picture-in-Picture player floating—it can't do that on its own currently, maybe the pip protocol will change that).
|
||||
|
||||
The default binds largely come from my own experience using PaperWM, and from other compositors.
|
||||
They assume QWERTY.
|
||||
The binds are ordered in a way to gradually introduce you to different bind configuration concepts.
|
||||
|
||||
The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kbd> will move the focused window or column there.
|
||||
Adding <kbd>Shift</kbd> does an alternative action: for focus and movement it starts going across monitors, for resizes it starts acting on window height rather than width, etc.
|
||||
Workspace switching on <kbd>Mod</kbd><kbd>U</kbd>/<kbd>I</kbd> is one key up from <kbd>Mod</kbd><kbd>J</kbd>/<kbd>K</kbd> used for window switching.
|
||||
|
||||
Since <kbd>Alt</kbd> is a modifier in nested niri, binds with explicit <kbd>Alt</kbd> are mainly the ones only useful on the host, for example spawning a screen locker.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
This is a checklist of things to release a new niri version.
|
||||
|
||||
We'll use `26.04` as the example new version.
|
||||
When making a patch release, append the patch number like `26.04.1`.
|
||||
|
||||
## Prepare the release notes
|
||||
|
||||
Plan for a few days of work, this usually takes a while.
|
||||
|
||||
During this process, also check:
|
||||
|
||||
- that all additions are marked with "next release" on the wiki,
|
||||
- if anything needs updating in `README.md`.
|
||||
|
||||
## Bump version
|
||||
|
||||
We use `year.month.patch` versioning.
|
||||
If the month contains a leading zero, drop it from the crate version (Cargo requirement).
|
||||
|
||||
You can use the command from [cargo-edit](https://github.com/killercup/cargo-edit):
|
||||
|
||||
```
|
||||
cargo set-version 26.4.0
|
||||
```
|
||||
|
||||
Then, manually update version in:
|
||||
|
||||
- `[package.metadata.generate-rpm]` in Cargo.toml
|
||||
- Dependency example in `niri-ipc/README.md`
|
||||
- Dependency example in `niri-ipc/src/lib.rs`
|
||||
|
||||
Do a full text search for the old version to make sure there are no other places.
|
||||
|
||||
## Replace all "Since: next release" mentions
|
||||
|
||||
Do a full text search for `next release`, replace everything with the new version number.
|
||||
|
||||
## Build, test, push, and have the CI run
|
||||
|
||||
Run all tests:
|
||||
|
||||
```
|
||||
RUN_SLOW_TESTS=1 cargo test --release --all
|
||||
```
|
||||
|
||||
- Run `cargo package -p niri-ipc` and make sure it succeeds.
|
||||
- Make sure the CI passes.
|
||||
- Make sure the niri-git COPR build passes.
|
||||
|
||||
## Trigger the "Prepare release" workflow on GitHub Actions
|
||||
|
||||
Set the "Public version" input to a version like `26.04`.
|
||||
|
||||
This workflow will:
|
||||
|
||||
- do some pre-release checks like grepping the wiki for "next version",
|
||||
- make a vendored dependency archive,
|
||||
- build and test niri with that dependency archive,
|
||||
- draft a new GitHub release with the archive attached.
|
||||
It will NOT override an existing draft release with the same name so the release notes are safe.
|
||||
|
||||
Make sure it succeeds and grab the vendored dependency archive that it produces.
|
||||
|
||||
## Update the niri COPR spec, update licenses in .spec.rpkg
|
||||
|
||||
You can grab the previous spec from [the last build](https://copr.fedorainfracloud.org/coprs/yalter/niri/builds/) in the COPR.
|
||||
|
||||
- Update version global to `26.04`.
|
||||
- Update commit global to the commit hash corresponding to the release commit.
|
||||
You can use `git rev-parse HEAD`.
|
||||
- Reset the `Release:` number to 1 if it was higher.
|
||||
|
||||
To run a test build, you can download the vendored dependency archive from the last step.
|
||||
Comment/uncomment `Source:` and `%autosetup` lines accordingly.
|
||||
|
||||
Download the source files:
|
||||
|
||||
```
|
||||
spectool -g niri.spec
|
||||
```
|
||||
|
||||
Build RPMs:
|
||||
|
||||
```
|
||||
fedpkg --release 44 mockbuild
|
||||
```
|
||||
|
||||
During the build, it will print the list of licenses.
|
||||
Update it in both the COPR spec and in `niri.spec.rpkg` accordingly.
|
||||
|
||||
If you had to update `niri.spec.rpkg` and therefore make another commit to the niri repo, make sure to update the commit hash in the COPR spec again.
|
||||
|
||||
Revert any temporary changes that you did to the COPR spec for local testing.
|
||||
|
||||
## Create and push the release git tag
|
||||
|
||||
The tag starts with a `v`:
|
||||
|
||||
```
|
||||
git tag -am "v26.04 release" v26.04
|
||||
git push origin v26.04
|
||||
```
|
||||
|
||||
While you can let GitHub create the tag automatically upon creating the release, this is not recommended.
|
||||
GitHub creates a *lightweight* tag, but we want an annotated tag that plays better with various tooling.
|
||||
|
||||
## Publish the release on GitHub
|
||||
|
||||
- Either upload the vendored dependencies file to your draft release with the release notes, or move the release notes to the GitHub-created release (the difference is that it's attributed to github-actions).
|
||||
- Set the tag to `v26.04`.
|
||||
- Set the release title to `v26.04`.
|
||||
- Check "Create a discussion for this release".
|
||||
|
||||
## Publish the niri-ipc crate
|
||||
|
||||
```
|
||||
cargo publish -p niri-ipc
|
||||
```
|
||||
|
||||
## Kick off the COPR build
|
||||
|
||||
Upload on the web or:
|
||||
|
||||
```
|
||||
copr-cli build niri niri.spec
|
||||
```
|
||||
|
||||
## Announce the release
|
||||
|
||||
Chat rooms, social media, etc.
|
||||
|
||||
## Update wayland.app protocol data
|
||||
|
||||
- Install [wlprobe](https://github.com/PolyMeilex/wlprobe).
|
||||
- Clone https://github.com/vially/wayland-explorer.
|
||||
- Generate data:
|
||||
|
||||
```
|
||||
wlprobe > ./src/data/compositors/niri.json
|
||||
```
|
||||
|
||||
- Manually add `"version": "26.04"`, then clean up the diff from unrelated changes, for example:
|
||||
- The number of `wl_output`s will change depending on how many monitors you have connected.
|
||||
- The number of `wp_drm_lease_device_v1` will change depending on your number of GPUs.
|
||||
- `org_kde_kwin_server_decoration_manager` and `zxdg_decoration_manager_v1` will only appear with `prefer-no-csd`.
|
||||
- Create a pull request.
|
||||
+85
-9
@@ -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?
|
||||
|
||||
@@ -60,20 +74,18 @@ A combination of factors:
|
||||
|
||||
All in all, the situation works out in favor of avoiding Xwayland integration.
|
||||
|
||||
Also, in the next release niri will have seamless built-in xwayland-satellite integration, that will solve the big rough edge of having to set it up manually.
|
||||
<sup>Since: 25.08</sup> niri has seamless built-in xwayland-satellite integration that by and large works as well as built-in Xwayland in other compositors, solving the hurdle of having to set it up manually.
|
||||
|
||||
Besides, I wouldn't be too surprised if, down the road, xwayland-satellite becomes the standard way of integrating Xwayland into new compositors, since it takes on the bulk of the annoying work, and isolates the compositor from misbehaving clients.
|
||||
I wouldn't be too surprised if, down the road, xwayland-satellite becomes the standard way of integrating Xwayland into new compositors, since it takes on the bulk of the annoying work, and isolates the compositor from misbehaving clients.
|
||||
|
||||
### 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
|
||||
@@ -60,6 +60,12 @@ Switch workspaces with three-finger vertical swipes.
|
||||
|
||||
Move the view horizontally with three-finger horizontal swipes.
|
||||
|
||||
#### Open and Close the Overview
|
||||
|
||||
<sup>Since: 25.05</sup>
|
||||
|
||||
Open and close the overview with a four-finger vertical swipe.
|
||||
|
||||
### All Pointing Devices
|
||||
|
||||
#### Drag-and-Drop Edge View Scroll
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
This page contains various bits of information helpful for integrating niri in a distribution.
|
||||
First, for creating a niri package, see the [Packaging](./Packaging-niri.md) page.
|
||||
|
||||
### 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/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.
|
||||
|
||||
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
|
||||
|
||||
Xwayland is required for running X11 apps and games, and also the Orca screen reader.
|
||||
|
||||
<sup>Since: 25.08</sup> Niri integrates with [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) out of the box.
|
||||
The integration requires xwayland-satellite >= 0.7 available in `$PATH`.
|
||||
Please consider making niri depend on (or at least recommend) the xwayland-satellite package.
|
||||
If you had a custom config which manually started `xwayland-satellite` and set `$DISPLAY`, you should remove those customizations for the automatic integration to work.
|
||||
|
||||
You can change the path where niri looks for xwayland-satellite using the [`xwayland-satellite` top-level option](./Configuration:-Miscellaneous.md#xwayland-satellite).
|
||||
|
||||
### Keyboard layout
|
||||
|
||||
<sup>Since: 25.08</sup> By default (unless [manually configured](./Configuration:-Input.md#layout) otherwise), niri reads keyboard layout settings from systemd-localed at `org.freedesktop.locale1` over D-Bus.
|
||||
Make sure your system installer sets the keyboard layout via systemd-localed, and niri should pick it up.
|
||||
|
||||
### Autostart
|
||||
|
||||
Niri works with the normal systemd autostart.
|
||||
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.
|
||||
|
||||
If this is inconvenient, you can also add [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup) lines in the niri config.
|
||||
|
||||
### Screen readers
|
||||
|
||||
<sup>Since: 25.08</sup> Niri works with the [Orca](https://orca.gnome.org) screen reader.
|
||||
Please see the [Accessibility](./Accessibility.md) page for details and advice for accessibility-focused distributions.
|
||||
|
||||
### Desktop components
|
||||
|
||||
You very likely want to run at least a notification daemon, portals, and an authentication agent.
|
||||
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 [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://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).
|
||||
|
||||
### Security model
|
||||
|
||||
See the [Security Model](./Security-Model.md) page for an overview of niri's security model.
|
||||
@@ -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/).
|
||||
+2
-2
@@ -42,7 +42,7 @@ The fix shipped in the driver at the time of writing uses a value of 0, while th
|
||||
|
||||
### Screencast flickering fix
|
||||
|
||||
<sup>Until: next release</sup>
|
||||
<sup>Until: 25.08</sup>
|
||||
|
||||
If you have screencast glitches or flickering on NVIDIA, set this in the niri config:
|
||||
|
||||
@@ -52,4 +52,4 @@ debug {
|
||||
}
|
||||
```
|
||||
|
||||
This will become unnecessary once niri supports explicit sync for PipeWire screencasts: https://github.com/YaLTeR/niri/issues/1432
|
||||
This debug flag has since been removed because the problem was properly fixed in niri.
|
||||
|
||||
@@ -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,10 +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.
|
||||
@@ -42,6 +63,13 @@ $ export RAYON_NUM_THREADS=2
|
||||
|
||||
Don't forget to exclude the development-only `niri-visual-tests` crate when running tests.
|
||||
|
||||
Some tests require surfaceless EGL to be available at test time.
|
||||
If this is problematic, you can skip them like so:
|
||||
|
||||
```
|
||||
$ cargo test -- --skip=::egl
|
||||
```
|
||||
|
||||
You may also want to set the `RUN_SLOW_TESTS=1` environment variable to run the slower tests.
|
||||
|
||||
### Version string
|
||||
@@ -121,3 +149,8 @@ It contains the exact dependency versions that I used when testing the release.
|
||||
If you need to change the versions of some dependencies, pay extra attention to `smithay` and `smithay-drm-extras` commit hash.
|
||||
These crates don't currently have regular stable releases, so niri uses git snapshots.
|
||||
Upstream frequently has breaking changes (API and behavior), so you're strongly advised to use the exact commit hash from the niri release's `Cargo.lock`.
|
||||
|
||||
### Shell completions
|
||||
|
||||
You can generate shell completions for several shells via `niri completions <SHELL>`, i.e. `niri completions bash`.
|
||||
See `niri completions -h` for a full list.
|
||||
|
||||
+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,61 @@
|
||||
Niri assumes that programs running unsandboxed on the host are **trusted**.
|
||||
|
||||
This is a reasonable assumption because programs running on the host have a wide variety of ways to get all access they need, even without niri.
|
||||
For instance:
|
||||
|
||||
- They can set `$LD_PRELOAD` in `.bashrc` or similar files to load an arbitrary library into all processes.
|
||||
- They can replace binaries in `$PATH` with malicious code.
|
||||
- They can interpose any socket in `$XDG_RUNTIME_DIR`, like Wayland, and do keylogging or record window contents.
|
||||
- They can scan the filesystem for secrets: SSH keys, password stores, etc.
|
||||
- They can connect to an unlocked keyring and steal credentials.
|
||||
- And so on and so forth.
|
||||
|
||||
## Unsandboxed clients
|
||||
|
||||
Anything with access to niri's Wayland socket can, among other things:
|
||||
|
||||
- Record the user's screen via [wlr-screencopy](https://wayland.app/protocols/wlr-screencopy-unstable-v1).
|
||||
- Emulate input via [wlr-virtual-pointer](https://wayland.app/protocols/wlr-virtual-pointer-unstable-v1) and [virtual-keyboard](https://wayland.app/protocols/virtual-keyboard-unstable-v1).
|
||||
- Get the user's clipboard contents via [wlr-data-control](https://wayland.app/protocols/ext-data-control-v1).
|
||||
- Create arbitrary fullscreen surfaces through [wlr-layer-shell](https://wayland.app/protocols/wlr-layer-shell-unstable-v1) that can steal the user's input, pretend to be a password entry, or lock the user out of their session.
|
||||
- Kill a running lockscreen, create a new lock surface, and tell niri to unlock a locked session.
|
||||
|
||||
Anything with access to niri's [IPC](./IPC.md) socket can, among other things:
|
||||
|
||||
- Spawn a Wayland client which can do everything in the list above.
|
||||
|
||||
Anything with access to niri's D-Bus interfaces can, among other things:
|
||||
|
||||
- Record the user's screen via the screencast interface.
|
||||
- Fully listen to and emulate input from the user's keyboard via the accessibility interface.
|
||||
|
||||
Also, while niri doesn't directly integrate Xwayland, it's worth reminding that anything with access to the X11 `$DISPLAY` (which comes both as a socket file on disk **and** as an abstract socket in the network namespace) can intercept and emulate all input and record the contents of any X11 windows on the same `$DISPLAY` (but not Wayland windows).
|
||||
|
||||
## Running untrusted clients
|
||||
|
||||
Considering all of the above, for running untrusted clients, you need a proper sandbox that:
|
||||
|
||||
- Removes niri's IPC socket.
|
||||
- Prevents D-Bus access to host services.
|
||||
- Uses a filtered Wayland socket.
|
||||
|
||||
For creating a filtered Wayland socket, you can use the [security-context](https://wayland.app/protocols/security-context-v1) protocol which niri implements.
|
||||
All unsafe protocols are made inaccessible through this filtered Wayland socket.
|
||||
|
||||
One sandbox that satisfies all of these criteria is the [Flatpak](https://flatpak.org/) sandbox.
|
||||
|
||||
Importantly, filtering just the Wayland socket (and leaving, for example, unrestricted D-Bus access) is **not enough** to prevent untrusted clients from doing bad things.
|
||||
|
||||
## Lock screen
|
||||
|
||||
When the session is locked via [ext-session-lock](https://wayland.app/protocols/ext-session-lock-v1), most actions (keybindings) are automatically disabled.
|
||||
Only a very small set of safe actions is allowed.
|
||||
In particular, spawning will not work, with the exception of binds explicitly configured with `allow-when-locked=true`.
|
||||
|
||||
Importantly, the **quit** action is allowed—you can always quit niri, even when on a lock screen.
|
||||
Therefore, you must ensure that quitting niri does not drop you into an unprotected TTY commandline.
|
||||
Usually, a display manager, like GDM, will do this for you: when niri exits (via the quit bind or if it crashes), it'll put you back into a safe password prompt.
|
||||
|
||||
Other than quitting, the only way to exit a lock screen is for the lock screen client to tell niri to unlock the session.
|
||||
If the lock screen client crashes, the session remains locked with a solid red background.
|
||||
In this case, another lock screen client can take over (so you can start a fresh lock screen if it crashes, and still unlock your session).
|
||||
@@ -0,0 +1,87 @@
|
||||
### 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.
|
||||
+31
-36
@@ -1,48 +1,35 @@
|
||||
X11 is very cursed, so built-in Xwayland support [is not planned at the moment](./FAQ.md#why-doesnt-niri-integrate-xwayland-like-other-compositors).
|
||||
However, there are multiple solutions to running X11 apps in niri.
|
||||
|
||||
## Using xwayland-satellite
|
||||
|
||||
[xwayland-satellite] implements rootless Xwayland in a separate application, without the host compositor's involvement.
|
||||
It makes X11 windows appear as normal windows, just like a native Xwayland integration.
|
||||
xwayland-satellite works well with most applications: Steam, games, Discord, even more exotic things like Ardour with wine Windows VST plugins.
|
||||
However, X11 apps that want to position windows or bars at specific screen coordinates won't behave correctly.
|
||||
<sup>Since: 25.08</sup> Niri integrates with [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) out of the box.
|
||||
Ensure xwayland-satellite >= 0.7 is installed and available in `$PATH`.
|
||||
With no further configuration, niri will create X11 sockets on disk, export `$DISPLAY`, and spawn xwayland-satellite on-demand when an X11 client connects.
|
||||
If xwayland-satellite dies, niri will automatically restart it.
|
||||
|
||||
> [!NOTE]
|
||||
> In the next release, niri will have [built-in xwayland-satellite integration](./Configuration:-Miscellaneous.md#xwayland-satellite).
|
||||
> You can try it by installing git versions of both niri and xwayland-satellite.
|
||||
> With no further configuration, niri will create X11 sockets, then when an X11 client connects, automatically start xwayland-satellite.
|
||||
>
|
||||
> This matches how other compositors run Xwayland (but in niri's case, it's xwayland-satellite rather than Xwayland itself).
|
||||
> It also makes X11 apps work fine in `spawn-at-startup` and in XDG autostart.
|
||||
If you had a custom config which manually started `xwayland-satellite` and set `$DISPLAY`, you should remove those customizations for the automatic integration to work.
|
||||
|
||||
Install it from your package manager, or build it according to instructions from its README, then run the `xwayland-satellite` binary.
|
||||
Look for a log message like: `Connected to Xwayland on :0`.
|
||||
Now you can start X11 applications on this X11 DISPLAY:
|
||||
To check that the integration works, verify that the niri output says something like `listening on X11 socket: :0`:
|
||||
|
||||
```
|
||||
env DISPLAY=:0 flatpak run com.valvesoftware.Steam
|
||||
```sh
|
||||
$ journalctl --user-unit=niri -b
|
||||
systemd[2338]: Starting niri.service - A scrollable-tiling Wayland compositor...
|
||||
niri[2474]: 2025-08-29T04:07:40.043402Z INFO niri: starting version 25.05.1 (0.0.git.2345.d9833fc1)
|
||||
(...)
|
||||
niri[2474]: 2025-08-29T04:07:40.690512Z INFO niri: listening on Wayland socket: wayland-1
|
||||
niri[2474]: 2025-08-29T04:07:40.690520Z INFO niri: IPC listening on: /run/user/1000/niri.wayland-1.2474.sock
|
||||
niri[2474]: 2025-08-29T04:07:40.700137Z INFO niri: listening on X11 socket: :0
|
||||
systemd[2338]: Started niri.service - A scrollable-tiling Wayland compositor.
|
||||
$ echo $DISPLAY
|
||||
:0
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can also automatically run it at startup, and set `DISPLAY` by default for all apps by adding it to the [`environment`](./Configuration:-Miscellaneous.md#environment) section of the niri config:
|
||||
We're using xwayland-satellite rather than Xwayland directly because [X11 is very cursed](./FAQ.md#why-doesnt-niri-integrate-xwayland-like-other-compositors).
|
||||
xwayland-satellite takes on the bulk of the work dealing with the X11 peculiarities from us, giving niri normal Wayland windows to manage.
|
||||
|
||||
```kdl
|
||||
spawn-at-startup "xwayland-satellite"
|
||||
// Or, if you built it by hand:
|
||||
// spawn-at-startup "~/path/to/code/target/release/xwayland-satellite"
|
||||
|
||||
environment {
|
||||
DISPLAY ":0"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If the `:0` DISPLAY is already taken (for example, by some other Xwayland server like `xwayland-run`), `xwayland-satellite` will try the next DISPLAY numbers in order: `:1`, `:2`, etc. and tell you which one it used in its output.
|
||||
> Then, you will need to use that DISPLAY number for the `env` command or for the niri [`environment`](./Configuration:-Miscellaneous.md#environment) section.
|
||||
>
|
||||
> You can also force a specific DISPLAY number like so: `xwayland-satellite :12` will start on `DISPLAY=:12`.
|
||||
xwayland-satellite works well with most applications: Steam, games, Discord, even more exotic things like Ardour with wine Windows VST plugins.
|
||||
However, X11 apps that want to position windows or bars at specific screen coordinates won't behave correctly and will need a nested compositor to run.
|
||||
See sections below for how to do that.
|
||||
|
||||
## Using the labwc Wayland compositor
|
||||
|
||||
@@ -60,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:
|
||||
|
||||
@@ -134,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,7 +13,13 @@
|
||||
* [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)
|
||||
* [Security Model](./Security-Model.md)
|
||||
* [Accessibility](./Accessibility.md)
|
||||
* [Name and Logo](./Name-and-Logo.md)
|
||||
* [FAQ](./FAQ.md)
|
||||
|
||||
## Configuration
|
||||
@@ -29,12 +35,15 @@
|
||||
* [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)
|
||||
* [Documenting niri](./Development:-Documenting-niri.md)
|
||||
* [Releasing niri](./Development:-Releasing-niri.md)
|
||||
* [Fractional Layout](./Development:-Fractional-Layout.md)
|
||||
* [Redraw Loop](./Development:-Redraw-Loop.md)
|
||||
* [Animation Timing](./Development:-Animation-Timing.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:44cbf3144d7add741762a8034779602f6a94b2b99a6d62774c9d75f3606b98b1
|
||||
size 1582702
|
||||
@@ -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.
|
||||
@@ -118,7 +119,7 @@
|
||||
checkFlags = [
|
||||
# These tests require the ability to access a "valid EGL Display", but that won't work
|
||||
# inside the Nix sandbox
|
||||
"--skip=tests::animations"
|
||||
"--skip=::egl"
|
||||
];
|
||||
|
||||
postInstall =
|
||||
@@ -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;
|
||||
@@ -204,6 +207,7 @@
|
||||
];
|
||||
}
|
||||
))
|
||||
pkgs.cargo-insta
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -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.5.1", path = "../niri-ipc" }
|
||||
regex = "1.11.1"
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,837 @@
|
||||
use knuffel::errors::DecodeError;
|
||||
use knuffel::Decode as _;
|
||||
|
||||
use crate::utils::{expect_only_children, parse_arg_node, MergeWith};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Animations {
|
||||
pub off: bool,
|
||||
pub slowdown: f64,
|
||||
pub workspace_switch: WorkspaceSwitchAnim,
|
||||
pub window_open: WindowOpenAnim,
|
||||
pub window_close: WindowCloseAnim,
|
||||
pub horizontal_view_movement: HorizontalViewMovementAnim,
|
||||
pub window_movement: WindowMovementAnim,
|
||||
pub window_resize: WindowResizeAnim,
|
||||
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
|
||||
pub exit_confirmation_open_close: ExitConfirmationOpenCloseAnim,
|
||||
pub screenshot_ui_open: ScreenshotUiOpenAnim,
|
||||
pub overview_open_close: OverviewOpenCloseAnim,
|
||||
pub recent_windows_close: RecentWindowsCloseAnim,
|
||||
}
|
||||
|
||||
impl Default for Animations {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
slowdown: 1.,
|
||||
workspace_switch: Default::default(),
|
||||
horizontal_view_movement: Default::default(),
|
||||
window_movement: Default::default(),
|
||||
window_open: Default::default(),
|
||||
window_close: Default::default(),
|
||||
window_resize: Default::default(),
|
||||
config_notification_open_close: Default::default(),
|
||||
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,
|
||||
pub kind: Kind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Kind {
|
||||
Easing(EasingParams),
|
||||
Spring(SpringParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct EasingParams {
|
||||
pub duration_ms: u32,
|
||||
pub curve: Curve,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Curve {
|
||||
Linear,
|
||||
EaseOutQuad,
|
||||
EaseOutCubic,
|
||||
EaseOutExpo,
|
||||
CubicBezier(f64, f64, f64, f64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct SpringParams {
|
||||
pub damping_ratio: f64,
|
||||
pub stiffness: u32,
|
||||
pub epsilon: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct WorkspaceSwitchAnim(pub Animation);
|
||||
|
||||
impl Default for WorkspaceSwitchAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 1000,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowOpenAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowOpenAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: Kind::Easing(EasingParams {
|
||||
duration_ms: 150,
|
||||
curve: Curve::EaseOutExpo,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowCloseAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: Kind::Easing(EasingParams {
|
||||
duration_ms: 150,
|
||||
curve: Curve::EaseOutQuad,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct HorizontalViewMovementAnim(pub Animation);
|
||||
|
||||
impl Default for HorizontalViewMovementAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct WindowMovementAnim(pub Animation);
|
||||
|
||||
impl Default for WindowMovementAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowResizeAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowResizeAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ConfigNotificationOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for ConfigNotificationOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 0.6,
|
||||
stiffness: 1000,
|
||||
epsilon: 0.001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ExitConfirmationOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for ExitConfirmationOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 0.6,
|
||||
stiffness: 500,
|
||||
epsilon: 0.01,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ScreenshotUiOpenAnim(pub Animation);
|
||||
|
||||
impl Default for ScreenshotUiOpenAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Easing(EasingParams {
|
||||
duration_ms: 200,
|
||||
curve: Curve::EaseOutQuad,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct OverviewOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for OverviewOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: Kind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
{
|
||||
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<S> knuffel::Decode<S> for HorizontalViewMovementAnim
|
||||
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<S> knuffel::Decode<S> for WindowMovementAnim
|
||||
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<S> knuffel::Decode<S> for WindowOpenAnim
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for WindowCloseAnim
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for WindowResizeAnim
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ConfigNotificationOpenCloseAnim
|
||||
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<S> knuffel::Decode<S> for ExitConfirmationOpenCloseAnim
|
||||
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<S> knuffel::Decode<S> for ScreenshotUiOpenAnim
|
||||
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<S> knuffel::Decode<S> for OverviewOpenCloseAnim
|
||||
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<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 {
|
||||
off: true,
|
||||
kind: Kind::Easing(EasingParams {
|
||||
duration_ms: 0,
|
||||
curve: Curve::Linear,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_node<S: knuffel::traits::ErrorSpan>(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
default: Self,
|
||||
mut process_children: impl FnMut(
|
||||
&knuffel::ast::SpannedNode<S>,
|
||||
&mut knuffel::decode::Context<S>,
|
||||
) -> Result<bool, DecodeError<S>>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
#[derive(Default, PartialEq)]
|
||||
struct OptionalEasingParams {
|
||||
duration_ms: Option<u32>,
|
||||
curve: Option<Curve>,
|
||||
}
|
||||
|
||||
expect_only_children(node, ctx);
|
||||
|
||||
let mut off = false;
|
||||
let mut easing_params = OptionalEasingParams::default();
|
||||
let mut spring_params = None;
|
||||
|
||||
for child in node.children() {
|
||||
match &**child.node_name {
|
||||
"off" => {
|
||||
knuffel::decode::check_flag_node(child, ctx);
|
||||
if off {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `off`, single node expected",
|
||||
));
|
||||
} else {
|
||||
off = true;
|
||||
}
|
||||
}
|
||||
"spring" => {
|
||||
if easing_params != OptionalEasingParams::default() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `spring`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
spring_params = Some(SpringParams::decode_node(child, ctx)?);
|
||||
}
|
||||
"duration-ms" => {
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if easing_params.duration_ms.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `duration-ms`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
easing_params.duration_ms = Some(parse_arg_node("duration-ms", child, ctx)?);
|
||||
}
|
||||
"curve" => {
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if easing_params.curve.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `curve`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
let mut iter_args = child.arguments.iter();
|
||||
let val = iter_args.next().ok_or_else(|| {
|
||||
DecodeError::missing(child, "additional argument `curve` is required")
|
||||
})?;
|
||||
let animation_curve_string: String =
|
||||
knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
|
||||
let animation_curve = match animation_curve_string.as_str() {
|
||||
"linear" => Some(Curve::Linear),
|
||||
"ease-out-quad" => Some(Curve::EaseOutQuad),
|
||||
"ease-out-cubic" => Some(Curve::EaseOutCubic),
|
||||
"ease-out-expo" => Some(Curve::EaseOutExpo),
|
||||
"cubic-bezier" => {
|
||||
let val = iter_args.next().ok_or_else(|| {
|
||||
DecodeError::missing(
|
||||
child,
|
||||
"missing x1 coordinate for cubic Bézier curve control point",
|
||||
)
|
||||
})?;
|
||||
// the X axis represents time frame so it cannot be negative
|
||||
// or larger than 1
|
||||
let x1: FloatOrInt<0, 1> =
|
||||
knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
let val = iter_args.next().ok_or_else(|| {
|
||||
DecodeError::missing(
|
||||
child,
|
||||
"missing y1 coordinate for cubic Bézier curve control point",
|
||||
)
|
||||
})?;
|
||||
let y1: FloatOrInt<{ i32::MIN }, { i32::MAX }> =
|
||||
knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
let val = iter_args.next().ok_or_else(|| {
|
||||
DecodeError::missing(
|
||||
child,
|
||||
"missing x2 coordinate for cubic Bézier curve control point",
|
||||
)
|
||||
})?;
|
||||
let x2: FloatOrInt<0, 1> =
|
||||
knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
let val = iter_args.next().ok_or_else(|| {
|
||||
DecodeError::missing(
|
||||
child,
|
||||
"missing y2 coordinate for cubic Bézier curve control point",
|
||||
)
|
||||
})?;
|
||||
let y2: FloatOrInt<{ i32::MIN }, { i32::MAX }> =
|
||||
knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
|
||||
Some(Curve::CubicBezier(x1.0, y1.0, x2.0, y2.0))
|
||||
}
|
||||
unexpected_curve => {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
format!(
|
||||
"unexpected animation curve `{unexpected_curve}`. \
|
||||
Niri only supports five animation curves: \
|
||||
`ease-out-quad`, `ease-out-cubic`, `ease-out-expo`, `linear` and `cubic-bezier`."
|
||||
),
|
||||
));
|
||||
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(val) = iter_args.next() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
"unexpected argument",
|
||||
));
|
||||
}
|
||||
for name in child.properties.keys() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name.escape_default()),
|
||||
));
|
||||
}
|
||||
for child in child.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
easing_params.curve = animation_curve;
|
||||
}
|
||||
name_str => {
|
||||
if !process_children(child, ctx)? {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let kind = if let Some(spring_params) = spring_params {
|
||||
// Configured spring.
|
||||
Kind::Spring(spring_params)
|
||||
} else if easing_params == OptionalEasingParams::default() {
|
||||
// Did not configure anything.
|
||||
default.kind
|
||||
} else {
|
||||
// Configured easing.
|
||||
let default = if let Kind::Easing(easing) = default.kind {
|
||||
easing
|
||||
} else {
|
||||
// Generic fallback values for when the default animation is spring, but the user
|
||||
// configured an easing animation.
|
||||
EasingParams {
|
||||
duration_ms: 250,
|
||||
curve: Curve::EaseOutCubic,
|
||||
}
|
||||
};
|
||||
|
||||
Kind::Easing(EasingParams {
|
||||
duration_ms: easing_params.duration_ms.unwrap_or(default.duration_ms),
|
||||
curve: easing_params.curve.unwrap_or(default.curve),
|
||||
})
|
||||
};
|
||||
|
||||
Ok(Self { off, kind })
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for SpringParams
|
||||
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",
|
||||
));
|
||||
}
|
||||
if let Some(val) = node.arguments.first() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
"unexpected argument",
|
||||
));
|
||||
}
|
||||
for child in node.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut damping_ratio = None;
|
||||
let mut stiffness = None;
|
||||
let mut epsilon = None;
|
||||
for (name, val) in &node.properties {
|
||||
match &***name {
|
||||
"damping-ratio" => {
|
||||
damping_ratio = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
"stiffness" => {
|
||||
stiffness = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
"epsilon" => {
|
||||
epsilon = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
name_str => {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let damping_ratio = damping_ratio
|
||||
.ok_or_else(|| DecodeError::missing(node, "property `damping-ratio` is required"))?;
|
||||
let stiffness = stiffness
|
||||
.ok_or_else(|| DecodeError::missing(node, "property `stiffness` is required"))?;
|
||||
let epsilon =
|
||||
epsilon.ok_or_else(|| DecodeError::missing(node, "property `epsilon` is required"))?;
|
||||
|
||||
if !(0.1..=10.).contains(&damping_ratio) {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
node,
|
||||
"damping-ratio must be between 0.1 and 10.0",
|
||||
));
|
||||
}
|
||||
if stiffness < 1 {
|
||||
ctx.emit_error(DecodeError::conversion(node, "stiffness must be >= 1"));
|
||||
}
|
||||
if !(0.00001..=0.1).contains(&epsilon) {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
node,
|
||||
"epsilon must be between 0.00001 and 0.1",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(SpringParams {
|
||||
damping_ratio,
|
||||
stiffness,
|
||||
epsilon,
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
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: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub wait_for_frame_completion_before_queueing: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub enable_overlay_planes: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_cursor_plane: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_direct_scanout: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub keep_max_bpc_unchanged: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
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: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub emulate_zero_presentation_time: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_resize_throttling: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_transactions: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub keep_laptop_panel_on_when_lid_is_closed: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub disable_monitor_names: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub strict_new_window_focus_policy: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub honor_xdg_activation_with_invalid_serial: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub deactivate_unfocused_windows: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
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)]
|
||||
pub enum PreviewRender {
|
||||
Screencast,
|
||||
ScreenCapture,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use crate::utils::MergeWith;
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Gestures {
|
||||
pub dnd_edge_view_scroll: DndEdgeViewScroll,
|
||||
pub dnd_edge_workspace_switch: DndEdgeWorkspaceSwitch,
|
||||
pub hot_corners: HotCorners,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub trigger_width: f64,
|
||||
pub delay_ms: u16,
|
||||
pub max_speed: f64,
|
||||
}
|
||||
|
||||
impl Default for DndEdgeViewScroll {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
trigger_width: 30., // Taken from GTK 4.
|
||||
delay_ms: 100,
|
||||
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 {
|
||||
pub trigger_height: f64,
|
||||
pub delay_ms: u16,
|
||||
pub max_speed: f64,
|
||||
}
|
||||
|
||||
impl Default for DndEdgeWorkspaceSwitch {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
trigger_height: 50.,
|
||||
delay_ms: 100,
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,749 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use miette::miette;
|
||||
use smithay::input::keyboard::XkbConfig;
|
||||
use smithay::reexports::input;
|
||||
|
||||
use crate::binds::Modifiers;
|
||||
use crate::utils::{Flag, MergeWith, Percent};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Input {
|
||||
pub keyboard: Keyboard,
|
||||
pub touchpad: Touchpad,
|
||||
pub mouse: Mouse,
|
||||
pub trackpoint: Trackpoint,
|
||||
pub trackball: Trackball,
|
||||
pub tablet: Tablet,
|
||||
pub touch: Touch,
|
||||
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: Option<Flag>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mod_key: Option<ModKey>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mod_key_nested: Option<ModKey>,
|
||||
}
|
||||
|
||||
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 {
|
||||
pub xkb: Xkb,
|
||||
pub repeat_delay: u16,
|
||||
pub repeat_rate: u8,
|
||||
pub track_layout: TrackLayout,
|
||||
pub numlock: bool,
|
||||
}
|
||||
|
||||
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(),
|
||||
numlock: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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)]
|
||||
pub rules: String,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub model: String,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub layout: String,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub variant: String,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub options: Option<String>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub file: Option<String>,
|
||||
}
|
||||
|
||||
impl Xkb {
|
||||
pub fn to_xkb_config(&self) -> XkbConfig<'_> {
|
||||
XkbConfig {
|
||||
rules: &self.rules,
|
||||
model: &self.model,
|
||||
layout: &self.layout,
|
||||
variant: &self.variant,
|
||||
options: self.options.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackLayout {
|
||||
/// The layout change is global.
|
||||
#[default]
|
||||
Global,
|
||||
/// The layout change is window local.
|
||||
Window,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct ScrollFactor {
|
||||
#[knuffel(argument)]
|
||||
pub base: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(property)]
|
||||
pub horizontal: Option<FloatOrInt<-100, 100>>,
|
||||
#[knuffel(property)]
|
||||
pub vertical: Option<FloatOrInt<-100, 100>>,
|
||||
}
|
||||
|
||||
impl ScrollFactor {
|
||||
pub fn h_v_factors(&self) -> (f64, f64) {
|
||||
let base_value = self.base.map(|f| f.0).unwrap_or(1.0);
|
||||
let h = self.horizontal.map(|f| f.0).unwrap_or(base_value);
|
||||
let v = self.vertical.map(|f| f.0).unwrap_or(base_value);
|
||||
(h, v)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Touchpad {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub tap: bool,
|
||||
#[knuffel(child)]
|
||||
pub dwt: bool,
|
||||
#[knuffel(child)]
|
||||
pub dwtp: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub drag: Option<bool>,
|
||||
#[knuffel(child)]
|
||||
pub drag_lock: bool,
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub click_method: Option<ClickMethod>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: FloatOrInt<-1, 1>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scroll_button: Option<u32>,
|
||||
#[knuffel(child)]
|
||||
pub scroll_button_lock: bool,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub tap_button_map: Option<TapButtonMap>,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
#[knuffel(child)]
|
||||
pub disabled_on_external_mouse: bool,
|
||||
#[knuffel(child)]
|
||||
pub middle_emulation: bool,
|
||||
#[knuffel(child)]
|
||||
pub scroll_factor: Option<ScrollFactor>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Mouse {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: FloatOrInt<-1, 1>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scroll_button: Option<u32>,
|
||||
#[knuffel(child)]
|
||||
pub scroll_button_lock: bool,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
#[knuffel(child)]
|
||||
pub middle_emulation: bool,
|
||||
#[knuffel(child)]
|
||||
pub scroll_factor: Option<ScrollFactor>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Trackpoint {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: FloatOrInt<-1, 1>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scroll_button: Option<u32>,
|
||||
#[knuffel(child)]
|
||||
pub scroll_button_lock: bool,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
#[knuffel(child)]
|
||||
pub middle_emulation: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Trackball {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: FloatOrInt<-1, 1>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scroll_button: Option<u32>,
|
||||
#[knuffel(child)]
|
||||
pub scroll_button_lock: bool,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
#[knuffel(child)]
|
||||
pub middle_emulation: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClickMethod {
|
||||
Clickfinger,
|
||||
ButtonAreas,
|
||||
}
|
||||
|
||||
impl From<ClickMethod> for input::ClickMethod {
|
||||
fn from(value: ClickMethod) -> Self {
|
||||
match value {
|
||||
ClickMethod::Clickfinger => Self::Clickfinger,
|
||||
ClickMethod::ButtonAreas => Self::ButtonAreas,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AccelProfile {
|
||||
Adaptive,
|
||||
Flat,
|
||||
}
|
||||
|
||||
impl From<AccelProfile> for input::AccelProfile {
|
||||
fn from(value: AccelProfile) -> Self {
|
||||
match value {
|
||||
AccelProfile::Adaptive => Self::Adaptive,
|
||||
AccelProfile::Flat => Self::Flat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScrollMethod {
|
||||
NoScroll,
|
||||
TwoFinger,
|
||||
Edge,
|
||||
OnButtonDown,
|
||||
}
|
||||
|
||||
impl From<ScrollMethod> for input::ScrollMethod {
|
||||
fn from(value: ScrollMethod) -> Self {
|
||||
match value {
|
||||
ScrollMethod::NoScroll => Self::NoScroll,
|
||||
ScrollMethod::TwoFinger => Self::TwoFinger,
|
||||
ScrollMethod::Edge => Self::Edge,
|
||||
ScrollMethod::OnButtonDown => Self::OnButtonDown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TapButtonMap {
|
||||
LeftRightMiddle,
|
||||
LeftMiddleRight,
|
||||
}
|
||||
|
||||
impl From<TapButtonMap> for input::TapButtonMap {
|
||||
fn from(value: TapButtonMap) -> Self {
|
||||
match value {
|
||||
TapButtonMap::LeftRightMiddle => Self::LeftRightMiddle,
|
||||
TapButtonMap::LeftMiddleRight => Self::LeftMiddleRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct Tablet {
|
||||
#[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>,
|
||||
#[knuffel(child)]
|
||||
pub map_to_focused_output: bool,
|
||||
#[knuffel(child)]
|
||||
pub map_to_focused_window: bool,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FocusFollowsMouse {
|
||||
#[knuffel(property, str)]
|
||||
pub max_scroll_amount: Option<Percent>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct WarpMouseToFocus {
|
||||
#[knuffel(property, str)]
|
||||
pub mode: Option<WarpMouseToFocusMode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum WarpMouseToFocusMode {
|
||||
CenterXy,
|
||||
CenterXyAlways,
|
||||
}
|
||||
|
||||
impl FromStr for WarpMouseToFocusMode {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"center-xy" => Ok(Self::CenterXy),
|
||||
"center-xy-always" => Ok(Self::CenterXyAlways),
|
||||
_ => Err(miette!(
|
||||
r#"invalid mode for warp-mouse-to-focus, can be "center-xy" or "center-xy-always" (or leave unset for separate centering)"#
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ModKey {
|
||||
Ctrl,
|
||||
Shift,
|
||||
Alt,
|
||||
Super,
|
||||
IsoLevel3Shift,
|
||||
IsoLevel5Shift,
|
||||
}
|
||||
|
||||
impl ModKey {
|
||||
pub fn to_modifiers(&self) -> Modifiers {
|
||||
match self {
|
||||
ModKey::Ctrl => Modifiers::CTRL,
|
||||
ModKey::Shift => Modifiers::SHIFT,
|
||||
ModKey::Alt => Modifiers::ALT,
|
||||
ModKey::Super => Modifiers::SUPER,
|
||||
ModKey::IsoLevel3Shift => Modifiers::ISO_LEVEL3_SHIFT,
|
||||
ModKey::IsoLevel5Shift => Modifiers::ISO_LEVEL5_SHIFT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ModKey {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match &*s.to_ascii_lowercase() {
|
||||
"ctrl" | "control" => Ok(Self::Ctrl),
|
||||
"shift" => Ok(Self::Shift),
|
||||
"alt" => Ok(Self::Alt),
|
||||
"super" | "win" => Ok(Self::Super),
|
||||
"iso_level3_shift" | "mod5" => Ok(Self::IsoLevel3Shift),
|
||||
"iso_level5_shift" | "mod3" => Ok(Self::IsoLevel5Shift),
|
||||
_ => Err(miette!("invalid Mod key: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClickMethod {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"clickfinger" => Ok(Self::Clickfinger),
|
||||
"button-areas" => Ok(Self::ButtonAreas),
|
||||
_ => Err(miette!(
|
||||
r#"invalid click method, can be "button-areas" or "clickfinger""#
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AccelProfile {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"adaptive" => Ok(Self::Adaptive),
|
||||
"flat" => Ok(Self::Flat),
|
||||
_ => Err(miette!(
|
||||
r#"invalid accel profile, can be "adaptive" or "flat""#
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ScrollMethod {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"no-scroll" => Ok(Self::NoScroll),
|
||||
"two-finger" => Ok(Self::TwoFinger),
|
||||
"edge" => Ok(Self::Edge),
|
||||
"on-button-down" => Ok(Self::OnButtonDown),
|
||||
_ => Err(miette!(
|
||||
r#"invalid scroll method, can be "no-scroll", "two-finger", "edge", or "on-button-down""#
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TapButtonMap {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"left-right-middle" => Ok(Self::LeftRightMiddle),
|
||||
"left-middle-right" => Ok(Self::LeftMiddleRight),
|
||||
_ => Err(miette!(
|
||||
r#"invalid tap button map, can be "left-right-middle" or "left-middle-right""#
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[track_caller]
|
||||
fn do_parse(text: &str) -> Input {
|
||||
let part = knuffel::parse("test.kdl", text)
|
||||
.map_err(miette::Report::new)
|
||||
.unwrap();
|
||||
Input::from_part(&part)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_combined() {
|
||||
// Test combined scroll-factor syntax
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
mouse {
|
||||
scroll-factor 2.0
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor 1.5
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
1.5,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_split() {
|
||||
// Test split horizontal/vertical syntax
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
mouse {
|
||||
scroll-factor horizontal=2.0 vertical=-1.0
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor horizontal=-1.5 vertical=0.5
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
-1.0,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
-1.5,
|
||||
),
|
||||
),
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_partial() {
|
||||
// Test partial specification (only one axis)
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
mouse {
|
||||
scroll-factor horizontal=2.0
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor vertical=-1.5
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: None,
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
-1.5,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_mixed() {
|
||||
// Test mixed base + override syntax
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
mouse {
|
||||
scroll-factor 2 vertical=-1
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor 1.5 horizontal=3
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
-1.0,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
1.5,
|
||||
),
|
||||
),
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
3.0,
|
||||
),
|
||||
),
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scroll_factor_h_v_factors() {
|
||||
let sf = ScrollFactor {
|
||||
base: Some(FloatOrInt(2.0)),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
};
|
||||
assert_debug_snapshot!(sf.h_v_factors(), @r#"
|
||||
(
|
||||
2.0,
|
||||
2.0,
|
||||
)
|
||||
"#);
|
||||
|
||||
let sf = ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(FloatOrInt(3.0)),
|
||||
vertical: Some(FloatOrInt(-1.0)),
|
||||
};
|
||||
assert_debug_snapshot!(sf.h_v_factors(), @r#"
|
||||
(
|
||||
3.0,
|
||||
-1.0,
|
||||
)
|
||||
"#);
|
||||
|
||||
let sf = ScrollFactor {
|
||||
base: Some(FloatOrInt(2.0)),
|
||||
horizontal: Some(FloatOrInt(1.0)),
|
||||
vertical: None,
|
||||
};
|
||||
assert_debug_snapshot!(sf.h_v_factors(), @r"
|
||||
(
|
||||
1.0,
|
||||
2.0,
|
||||
)
|
||||
");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{BlockOutFrom, CornerRadius, RegexEq, 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 {
|
||||
@@ -19,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)]
|
||||
@@ -27,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>,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
use knuffel::errors::DecodeError;
|
||||
use niri_ipc::{ColumnDisplay, SizeChange};
|
||||
|
||||
use crate::appearance::{
|
||||
Border, FocusRing, InsertHint, Shadow, TabIndicator, DEFAULT_BACKGROUND_COLOR,
|
||||
};
|
||||
use crate::utils::{expect_only_children, Flag, MergeWith};
|
||||
use crate::{BorderRule, Color, FloatOrInt, InsertHintPart, ShadowRule, TabIndicatorPart};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Layout {
|
||||
pub focus_ring: FocusRing,
|
||||
pub border: Border,
|
||||
pub shadow: Shadow,
|
||||
pub tab_indicator: TabIndicator,
|
||||
pub insert_hint: InsertHint,
|
||||
pub preset_column_widths: Vec<PresetSize>,
|
||||
pub default_column_width: Option<PresetSize>,
|
||||
pub preset_window_heights: Vec<PresetSize>,
|
||||
pub center_focused_column: CenterFocusedColumn,
|
||||
pub always_center_single_column: bool,
|
||||
pub empty_workspace_above_first: bool,
|
||||
pub default_column_display: ColumnDisplay,
|
||||
pub gaps: f64,
|
||||
pub struts: Struts,
|
||||
pub background_color: Color,
|
||||
}
|
||||
|
||||
impl Default for Layout {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
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: 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),
|
||||
Fixed(#[knuffel(argument)] i32),
|
||||
}
|
||||
|
||||
impl From<PresetSize> for SizeChange {
|
||||
fn from(value: PresetSize) -> Self {
|
||||
match value {
|
||||
PresetSize::Proportion(prop) => SizeChange::SetProportion(prop * 100.),
|
||||
PresetSize::Fixed(fixed) => SizeChange::SetFixed(fixed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DefaultPresetSize(pub Option<PresetSize>);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Struts {
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub left: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub right: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub top: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub bottom: FloatOrInt<-65535, 65535>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum CenterFocusedColumn {
|
||||
/// Focusing a column will not center the column.
|
||||
#[default]
|
||||
Never,
|
||||
/// The focused column will always be centered.
|
||||
Always,
|
||||
/// Focusing a column will center it if it doesn't fit on the screen together with the
|
||||
/// previously focused column.
|
||||
OnOverflow,
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for DefaultPresetSize
|
||||
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 children = node.children();
|
||||
|
||||
if let Some(child) = children.next() {
|
||||
if let Some(unwanted_child) = children.next() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
unwanted_child,
|
||||
"node",
|
||||
"expected no more than one child",
|
||||
));
|
||||
}
|
||||
PresetSize::decode_node(child, ctx).map(Some).map(Self)
|
||||
} else {
|
||||
Ok(Self(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
+922
-4620
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
use crate::appearance::{Color, WorkspaceShadow, WorkspaceShadowPart, DEFAULT_BACKDROP_COLOR};
|
||||
use crate::utils::{Flag, MergeWith};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SpawnAtStartup {
|
||||
#[knuffel(arguments)]
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SpawnShAtStartup {
|
||||
#[knuffel(argument)]
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Cursor {
|
||||
pub xcursor_theme: String,
|
||||
pub xcursor_size: u8,
|
||||
pub hide_when_typing: bool,
|
||||
pub hide_after_inactive_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for Cursor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
xcursor_theme: String::from("default"),
|
||||
xcursor_size: 24,
|
||||
hide_when_typing: false,
|
||||
hide_after_inactive_ms: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub skip_at_startup: bool,
|
||||
pub hide_not_bound: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
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 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, 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 {
|
||||
pub zoom: f64,
|
||||
pub backdrop_color: Color,
|
||||
pub workspace_shadow: WorkspaceShadow,
|
||||
}
|
||||
|
||||
impl Default for Overview {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
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>);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnvironmentVariable {
|
||||
#[knuffel(node_name)]
|
||||
pub name: String,
|
||||
#[knuffel(argument)]
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct XwaylandSatellite {
|
||||
pub off: bool,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl Default for XwaylandSatellite {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
path: String::from("xwayland-satellite"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
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)]
|
||||
pub off: bool,
|
||||
#[knuffel(argument)]
|
||||
pub name: String,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scale: Option<FloatOrInt<0, 10>>,
|
||||
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
pub position: Option<Position>,
|
||||
#[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 {
|
||||
pub fn is_vrr_always_on(&self) -> bool {
|
||||
self.variable_refresh_rate == Some(Vrr { on_demand: false })
|
||||
}
|
||||
|
||||
pub fn is_vrr_on_demand(&self) -> bool {
|
||||
self.variable_refresh_rate == Some(Vrr { on_demand: true })
|
||||
}
|
||||
|
||||
pub fn is_vrr_always_off(&self) -> bool {
|
||||
self.variable_refresh_rate.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Output {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
focus_at_startup: false,
|
||||
name: String::new(),
|
||||
scale: None,
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
mode: None,
|
||||
modeline: None,
|
||||
variable_refresh_rate: None,
|
||||
background_color: None,
|
||||
backdrop_color: None,
|
||||
hot_corners: None,
|
||||
layout: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputName {
|
||||
pub connector: String,
|
||||
pub make: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub serial: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Position {
|
||||
#[knuffel(property)]
|
||||
pub x: i32,
|
||||
#[knuffel(property)]
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Default)]
|
||||
pub struct Vrr {
|
||||
#[knuffel(property, default = false)]
|
||||
pub on_demand: bool,
|
||||
}
|
||||
|
||||
impl FromIterator<Output> for Outputs {
|
||||
fn from_iter<T: IntoIterator<Item = Output>>(iter: T) -> Self {
|
||||
Self(Vec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
impl Outputs {
|
||||
pub fn find(&self, name: &OutputName) -> Option<&Output> {
|
||||
self.0.iter().find(|o| name.matches(&o.name))
|
||||
}
|
||||
|
||||
pub fn find_mut(&mut self, name: &OutputName) -> Option<&mut Output> {
|
||||
self.0.iter_mut().find(|o| name.matches(&o.name))
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputName {
|
||||
pub fn from_ipc_output(output: &niri_ipc::Output) -> Self {
|
||||
Self {
|
||||
connector: output.name.clone(),
|
||||
make: (output.make != "Unknown").then(|| output.make.clone()),
|
||||
model: (output.model != "Unknown").then(|| output.model.clone()),
|
||||
serial: output.serial.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an output description matching what Smithay's `Output::new()` does.
|
||||
pub fn format_description(&self) -> String {
|
||||
format!(
|
||||
"{} - {} - {}",
|
||||
self.make.as_deref().unwrap_or("Unknown"),
|
||||
self.model.as_deref().unwrap_or("Unknown"),
|
||||
self.connector,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an output name that will match by make/model/serial or, if they are missing, by
|
||||
/// connector.
|
||||
pub fn format_make_model_serial_or_connector(&self) -> String {
|
||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||
self.connector.to_string()
|
||||
} else {
|
||||
self.format_make_model_serial()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_make_model_serial(&self) -> String {
|
||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||
format!("{make} {model} {serial}")
|
||||
}
|
||||
|
||||
pub fn matches(&self, target: &str) -> bool {
|
||||
// Match by connector.
|
||||
if target.eq_ignore_ascii_case(&self.connector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no other fields are available, don't try to match by them.
|
||||
//
|
||||
// This is used by niri msg output.
|
||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match by "make model serial" with Unknown if something is missing.
|
||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||
|
||||
let Some(target_make) = target.get(..make.len()) else {
|
||||
return false;
|
||||
};
|
||||
let rest = &target[make.len()..];
|
||||
if !target_make.eq_ignore_ascii_case(make) {
|
||||
return false;
|
||||
}
|
||||
if !rest.starts_with(' ') {
|
||||
return false;
|
||||
}
|
||||
let rest = &rest[1..];
|
||||
|
||||
let Some(target_model) = rest.get(..model.len()) else {
|
||||
return false;
|
||||
};
|
||||
let rest = &rest[model.len()..];
|
||||
if !target_model.eq_ignore_ascii_case(model) {
|
||||
return false;
|
||||
}
|
||||
if !rest.starts_with(' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let rest = &rest[1..];
|
||||
if !rest.eq_ignore_ascii_case(serial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Similar in spirit to Ord, but I don't want to derive Eq to avoid mistakes (you should use
|
||||
// `Self::match`, not Eq).
|
||||
pub fn compare(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let self_missing_mms = self.make.is_none() && self.model.is_none() && self.serial.is_none();
|
||||
let other_missing_mms =
|
||||
other.make.is_none() && other.model.is_none() && other.serial.is_none();
|
||||
|
||||
match (self_missing_mms, other_missing_mms) {
|
||||
(true, true) => self.connector.cmp(&other.connector),
|
||||
(true, false) => std::cmp::Ordering::Greater,
|
||||
(false, true) => std::cmp::Ordering::Less,
|
||||
(false, false) => self
|
||||
.make
|
||||
.cmp(&other.make)
|
||||
.then_with(|| self.model.cmp(&other.model))
|
||||
.then_with(|| self.serial.cmp(&other.serial))
|
||||
.then_with(|| self.connector.cmp(&other.connector)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_mode() {
|
||||
assert_eq!(
|
||||
"2560x1600@165.004".parse::<ConfiguredMode>().unwrap(),
|
||||
ConfiguredMode {
|
||||
width: 2560,
|
||||
height: 1600,
|
||||
refresh: Some(165.004),
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"1920x1080".parse::<ConfiguredMode>().unwrap(),
|
||||
ConfiguredMode {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
refresh: None,
|
||||
},
|
||||
);
|
||||
|
||||
assert!("1920".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x1080@".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x1080@60Hz".parse::<ConfiguredMode>().is_err());
|
||||
}
|
||||
|
||||
fn make_output_name(
|
||||
connector: &str,
|
||||
make: Option<&str>,
|
||||
model: Option<&str>,
|
||||
serial: Option<&str>,
|
||||
) -> OutputName {
|
||||
OutputName {
|
||||
connector: connector.to_string(),
|
||||
make: make.map(|x| x.to_string()),
|
||||
model: model.map(|x| x.to_string()),
|
||||
serial: serial.map(|x| x.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_name_match() {
|
||||
fn check(
|
||||
target: &str,
|
||||
connector: &str,
|
||||
make: Option<&str>,
|
||||
model: Option<&str>,
|
||||
serial: Option<&str>,
|
||||
) -> bool {
|
||||
let name = make_output_name(connector, make, model, serial);
|
||||
name.matches(target)
|
||||
}
|
||||
|
||||
assert!(check("dp-2", "DP-2", None, None, None));
|
||||
assert!(!check("dp-1", "DP-2", None, None, None));
|
||||
assert!(check("dp-2", "DP-2", Some("a"), Some("b"), Some("c")));
|
||||
assert!(check(
|
||||
"some company some monitor 1234",
|
||||
"DP-2",
|
||||
Some("Some Company"),
|
||||
Some("Some Monitor"),
|
||||
Some("1234")
|
||||
));
|
||||
assert!(!check(
|
||||
"some other company some monitor 1234",
|
||||
"DP-2",
|
||||
Some("Some Company"),
|
||||
Some("Some Monitor"),
|
||||
Some("1234")
|
||||
));
|
||||
assert!(!check(
|
||||
"make model serial ",
|
||||
"DP-2",
|
||||
Some("make"),
|
||||
Some("model"),
|
||||
Some("serial")
|
||||
));
|
||||
assert!(check(
|
||||
"make serial",
|
||||
"DP-2",
|
||||
Some("make"),
|
||||
Some(""),
|
||||
Some("serial")
|
||||
));
|
||||
assert!(check(
|
||||
"make model unknown",
|
||||
"DP-2",
|
||||
Some("Make"),
|
||||
Some("Model"),
|
||||
None
|
||||
));
|
||||
assert!(check(
|
||||
"unknown unknown serial",
|
||||
"DP-2",
|
||||
None,
|
||||
None,
|
||||
Some("Serial")
|
||||
));
|
||||
assert!(!check("unknown unknown unknown", "DP-2", None, None, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_name_sorting() {
|
||||
let mut names = vec![
|
||||
make_output_name("DP-2", None, None, None),
|
||||
make_output_name("DP-1", None, None, None),
|
||||
make_output_name("DP-3", Some("B"), Some("A"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("B"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("A"), Some("B")),
|
||||
make_output_name("DP-3", None, Some("A"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), None, Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("A"), None),
|
||||
make_output_name("DP-5", Some("A"), Some("A"), Some("A")),
|
||||
make_output_name("DP-4", Some("A"), Some("A"), Some("A")),
|
||||
];
|
||||
names.sort_by(|a, b| a.compare(b));
|
||||
let names = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
format!(
|
||||
"{} | {}",
|
||||
name.format_make_model_serial_or_connector(),
|
||||
name.connector,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
names,
|
||||
@r#"
|
||||
[
|
||||
"Unknown A A | DP-3",
|
||||
"A Unknown A | DP-3",
|
||||
"A A Unknown | DP-3",
|
||||
"A A A | DP-4",
|
||||
"A A A | DP-5",
|
||||
"A A B | DP-3",
|
||||
"A B A | DP-3",
|
||||
"B A A | DP-3",
|
||||
"DP-1 | DP-1",
|
||||
"DP-2 | DP-2",
|
||||
]
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,28 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
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);
|
||||
|
||||
// MIN and MAX generics are only used during parsing to check the value.
|
||||
#[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);
|
||||
@@ -21,3 +42,166 @@ impl FromStr for RegexEq {
|
||||
Regex::from_str(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Percent {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some((value, empty)) = s.split_once('%') else {
|
||||
return Err(miette!("value must end with '%'"));
|
||||
};
|
||||
|
||||
if !empty.is_empty() {
|
||||
return Err(miette!("trailing characters after '%' are not allowed"));
|
||||
}
|
||||
|
||||
let value: f64 = value.parse().map_err(|_| miette!("error parsing value"))?;
|
||||
Ok(Percent(value / 100.))
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
fn type_check(
|
||||
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) {
|
||||
if let Some(type_name) = &type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_decode(
|
||||
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
match &**val {
|
||||
knuffel::ast::Literal::Int(ref value) => match value.try_into() {
|
||||
Ok(v) => {
|
||||
if (MIN..=MAX).contains(&v) {
|
||||
Ok(FloatOrInt(f64::from(v)))
|
||||
} else {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
val,
|
||||
format!("value must be between {MIN} and {MAX}"),
|
||||
));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.emit_error(DecodeError::conversion(val, e));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
},
|
||||
knuffel::ast::Literal::Decimal(ref value) => match value.try_into() {
|
||||
Ok(v) => {
|
||||
if (f64::from(MIN)..=f64::from(MAX)).contains(&v) {
|
||||
Ok(FloatOrInt(v))
|
||||
} else {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
val,
|
||||
format!("value must be between {MIN} and {MAX}"),
|
||||
));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.emit_error(DecodeError::conversion(val, e));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
ctx.emit_error(DecodeError::unsupported(
|
||||
val,
|
||||
"Unsupported value, only numbers are recognized",
|
||||
));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_only_children<S>(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
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",
|
||||
))
|
||||
}
|
||||
|
||||
for name in node.properties.keys() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
"no properties expected for this node",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_arg_node<S: knuffel::traits::ErrorSpan, T: knuffel::traits::DecodeScalar<S>>(
|
||||
name: &str,
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<T, DecodeError<S>> {
|
||||
let mut iter_args = node.arguments.iter();
|
||||
let val = iter_args.next().ok_or_else(|| {
|
||||
DecodeError::missing(node, format!("additional argument `{name}` is required"))
|
||||
})?;
|
||||
|
||||
let value = knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||
|
||||
if let Some(val) = iter_args.next() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
"unexpected argument",
|
||||
));
|
||||
}
|
||||
for name in node.properties.keys() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name.escape_default()),
|
||||
));
|
||||
}
|
||||
for child in node.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
use niri_ipc::ColumnDisplay;
|
||||
|
||||
use crate::appearance::{
|
||||
BackgroundEffect, BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule,
|
||||
TabIndicatorRule,
|
||||
};
|
||||
use crate::layout::DefaultPresetSize;
|
||||
use crate::utils::{MergeWith, RegexEq};
|
||||
use crate::FloatOrInt;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct WindowRule {
|
||||
#[knuffel(children(name = "match"))]
|
||||
pub matches: Vec<Match>,
|
||||
#[knuffel(children(name = "exclude"))]
|
||||
pub excludes: Vec<Match>,
|
||||
|
||||
// Rules applied at initial configure.
|
||||
#[knuffel(child)]
|
||||
pub default_column_width: Option<DefaultPresetSize>,
|
||||
#[knuffel(child)]
|
||||
pub default_window_height: Option<DefaultPresetSize>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_on_output: Option<String>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_on_workspace: Option<String>,
|
||||
#[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>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub open_focused: Option<bool>,
|
||||
|
||||
// Rules applied dynamically.
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub min_width: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub min_height: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_width: Option<u16>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_height: Option<u16>,
|
||||
|
||||
#[knuffel(child, default)]
|
||||
pub focus_ring: BorderRule,
|
||||
#[knuffel(child, default)]
|
||||
pub border: BorderRule,
|
||||
#[knuffel(child, default)]
|
||||
pub shadow: ShadowRule,
|
||||
#[knuffel(child, default)]
|
||||
pub tab_indicator: TabIndicatorRule,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub draw_border_with_background: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub opacity: Option<f32>,
|
||||
#[knuffel(child)]
|
||||
pub geometry_corner_radius: Option<CornerRadius>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub clip_to_geometry: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub baba_is_float: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub block_out_from: Option<BlockOutFrom>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub variable_refresh_rate: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub default_column_display: Option<ColumnDisplay>,
|
||||
#[knuffel(child)]
|
||||
pub default_floating_position: Option<FloatingPosition>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
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)]
|
||||
pub struct Match {
|
||||
#[knuffel(property, str)]
|
||||
pub app_id: Option<RegexEq>,
|
||||
#[knuffel(property, str)]
|
||||
pub title: Option<RegexEq>,
|
||||
#[knuffel(property)]
|
||||
pub is_active: Option<bool>,
|
||||
#[knuffel(property)]
|
||||
pub is_focused: Option<bool>,
|
||||
#[knuffel(property)]
|
||||
pub is_active_in_column: Option<bool>,
|
||||
#[knuffel(property)]
|
||||
pub is_floating: Option<bool>,
|
||||
#[knuffel(property)]
|
||||
pub is_window_cast_target: Option<bool>,
|
||||
#[knuffel(property)]
|
||||
pub is_urgent: Option<bool>,
|
||||
#[knuffel(property)]
|
||||
pub at_startup: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FloatingPosition {
|
||||
#[knuffel(property)]
|
||||
pub x: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(property)]
|
||||
pub y: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(property, default)]
|
||||
pub relative_to: RelativeTo,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RelativeTo {
|
||||
#[default]
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
use knuffel::errors::DecodeError;
|
||||
|
||||
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>>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) {
|
||||
if let Some(type_name) = &type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_decode(
|
||||
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<WorkspaceName, DecodeError<S>> {
|
||||
#[derive(Debug)]
|
||||
struct WorkspaceNameSet(Vec<String>);
|
||||
match &**val {
|
||||
knuffel::ast::Literal::String(ref s) => {
|
||||
let mut name_set: Vec<String> = match ctx.get::<WorkspaceNameSet>() {
|
||||
Some(h) => h.0.clone(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
if name_set.iter().any(|name| name.eq_ignore_ascii_case(s)) {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
val,
|
||||
"named workspace",
|
||||
format!("duplicate named workspace: {s}"),
|
||||
));
|
||||
return Ok(Self(String::new()));
|
||||
}
|
||||
|
||||
name_set.push(s.to_string());
|
||||
ctx.set(WorkspaceNameSet(name_set));
|
||||
Ok(Self(s.clone().into()))
|
||||
}
|
||||
_ => {
|
||||
ctx.emit_error(DecodeError::unsupported(
|
||||
val,
|
||||
"workspace names must be strings",
|
||||
));
|
||||
Ok(Self(String::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.5.1"
|
||||
niri-ipc = "=26.4.0"
|
||||
```
|
||||
|
||||
+528
-16
@@ -41,7 +41,7 @@
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! niri-ipc = "=25.5.1"
|
||||
//! 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 {},
|
||||
@@ -447,9 +483,23 @@ pub enum Action {
|
||||
/// Focus the previous workspace.
|
||||
FocusWorkspacePrevious {},
|
||||
/// Move the focused window to the workspace below.
|
||||
MoveWindowToWorkspaceDown {},
|
||||
MoveWindowToWorkspaceDown {
|
||||
/// Whether the focus should follow the target workspace.
|
||||
///
|
||||
/// If `true` (the default), the focus will follow the window to the new workspace. If
|
||||
/// `false`, the focus will remain on the original workspace.
|
||||
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||
focus: bool,
|
||||
},
|
||||
/// Move the focused window to the workspace above.
|
||||
MoveWindowToWorkspaceUp {},
|
||||
MoveWindowToWorkspaceUp {
|
||||
/// Whether the focus should follow the target workspace.
|
||||
///
|
||||
/// If `true` (the default), the focus will follow the window to the new workspace. If
|
||||
/// `false`, the focus will remain on the original workspace.
|
||||
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||
focus: bool,
|
||||
},
|
||||
/// Move a window to a workspace.
|
||||
#[cfg_attr(
|
||||
feature = "clap",
|
||||
@@ -663,6 +713,8 @@ pub enum Action {
|
||||
},
|
||||
/// Switch between preset column widths.
|
||||
SwitchPresetColumnWidth {},
|
||||
/// Switch between preset column widths backwards.
|
||||
SwitchPresetColumnWidthBack {},
|
||||
/// Switch between preset window widths.
|
||||
SwitchPresetWindowWidth {
|
||||
/// Id of the window whose width to switch.
|
||||
@@ -671,6 +723,14 @@ pub enum Action {
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: Option<u64>,
|
||||
},
|
||||
/// Switch between preset window widths backwards.
|
||||
SwitchPresetWindowWidthBack {
|
||||
/// Id of the window whose width to switch.
|
||||
///
|
||||
/// If `None`, uses the focused window.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: Option<u64>,
|
||||
},
|
||||
/// Switch between preset window heights.
|
||||
SwitchPresetWindowHeight {
|
||||
/// Id of the window whose height to switch.
|
||||
@@ -679,8 +739,24 @@ pub enum Action {
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: Option<u64>,
|
||||
},
|
||||
/// Switch between preset window heights backwards.
|
||||
SwitchPresetWindowHeightBack {
|
||||
/// Id of the window whose height to switch.
|
||||
///
|
||||
/// If `None`, uses the focused window.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: Option<u64>,
|
||||
},
|
||||
/// 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.
|
||||
@@ -773,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,
|
||||
},
|
||||
@@ -822,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.
|
||||
@@ -850,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.
|
||||
@@ -873,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.
|
||||
@@ -932,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.
|
||||
@@ -980,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))]
|
||||
@@ -1057,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.
|
||||
@@ -1163,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`].
|
||||
@@ -1312,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))]
|
||||
@@ -1378,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.
|
||||
@@ -1415,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 {
|
||||
@@ -1479,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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1542,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;
|
||||
|
||||
@@ -1583,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;
|
||||
|
||||
@@ -1595,3 +1959,151 @@ impl FromStr for ScaleToSet {
|
||||
Ok(Self::Specific(scale))
|
||||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
||||
#[test]
|
||||
fn parse_size_change() {
|
||||
assert_eq!(
|
||||
"10".parse::<SizeChange>().unwrap(),
|
||||
SizeChange::SetFixed(10),
|
||||
);
|
||||
assert_eq!(
|
||||
"+10".parse::<SizeChange>().unwrap(),
|
||||
SizeChange::AdjustFixed(10),
|
||||
);
|
||||
assert_eq!(
|
||||
"-10".parse::<SizeChange>().unwrap(),
|
||||
SizeChange::AdjustFixed(-10),
|
||||
);
|
||||
assert_eq!(
|
||||
"10%".parse::<SizeChange>().unwrap(),
|
||||
SizeChange::SetProportion(10.),
|
||||
);
|
||||
assert_eq!(
|
||||
"+10%".parse::<SizeChange>().unwrap(),
|
||||
SizeChange::AdjustProportion(10.),
|
||||
);
|
||||
assert_eq!(
|
||||
"-10%".parse::<SizeChange>().unwrap(),
|
||||
SizeChange::AdjustProportion(-10.),
|
||||
);
|
||||
|
||||
assert!("-".parse::<SizeChange>().is_err());
|
||||
assert!("10% ".parse::<SizeChange>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_position_change() {
|
||||
assert_eq!(
|
||||
"10".parse::<PositionChange>().unwrap(),
|
||||
PositionChange::SetFixed(10.),
|
||||
);
|
||||
assert_eq!(
|
||||
"+10".parse::<PositionChange>().unwrap(),
|
||||
PositionChange::AdjustFixed(10.),
|
||||
);
|
||||
assert_eq!(
|
||||
"-10".parse::<PositionChange>().unwrap(),
|
||||
PositionChange::AdjustFixed(-10.),
|
||||
);
|
||||
|
||||
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.5.1", path = ".." }
|
||||
niri-config = { version = "25.5.1", 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> {
|
||||
|
||||
+27
-7
@@ -33,10 +33,13 @@ Summary: Scrollable-tiling Wayland compositor
|
||||
|
||||
SourceLicense: GPL-3.0-or-later
|
||||
|
||||
# (MIT OR Apache-2.0) AND BSD-3-Clause
|
||||
# (MIT OR Apache-2.0) AND Unicode-3.0
|
||||
# 0BSD OR MIT OR Apache-2.0
|
||||
# Apache-2.0
|
||||
# Apache-2.0 OR BSL-1.0
|
||||
# Apache-2.0 AND MIT
|
||||
# Apache-2.0 OR MIT
|
||||
# Apache-2.0 OR MIT OR Unlicense
|
||||
# Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT
|
||||
# BSD-2-Clause
|
||||
# BSD-2-Clause OR Apache-2.0 OR MIT
|
||||
@@ -44,20 +47,18 @@ SourceLicense: GPL-3.0-or-later
|
||||
# GPL-3.0-or-later
|
||||
# ISC
|
||||
# MIT
|
||||
# MIT AND (MIT OR Apache-2.0)
|
||||
# MIT OR Apache-2.0
|
||||
# (MIT OR Apache-2.0) AND BSD-3-Clause
|
||||
# (MIT OR Apache-2.0) AND Unicode-3.0
|
||||
# MIT OR Apache-2.0 OR LGPL-2.1-or-later
|
||||
# MIT OR Apache-2.0 OR Zlib
|
||||
# MIT OR Zlib OR Apache-2.0
|
||||
# MPL-2.0
|
||||
# Unicode-3.0
|
||||
# Unlicense OR MIT
|
||||
# Zlib
|
||||
# Zlib OR Apache-2.0 OR MIT
|
||||
License: (0BSD OR MIT OR Apache-2.0) AND (Apache-2.0) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR MIT) 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 AND (MIT OR Apache-2.0)) AND (MIT OR Apache-2.0) AND ((MIT OR Apache-2.0) AND BSD-3-Clause) AND ((MIT OR Apache-2.0) AND Unicode-3.0) 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 OR Apache-2.0 OR MIT)
|
||||
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 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 (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 }}}
|
||||
|
||||
@@ -82,6 +83,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
|
||||
@@ -120,6 +129,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
|
||||
|
||||
@@ -129,6 +142,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
|
||||
@@ -146,6 +163,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 }}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user