Merge pull request #131 from mahkoh/jorth/toml
config: change default config to use toml-based configuration
This commit is contained in:
commit
a4559f5293
90 changed files with 15094 additions and 581 deletions
|
|
@ -6,6 +6,8 @@ tasks:
|
|||
sudo pacman -Syu --noconfirm
|
||||
sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake
|
||||
rustup toolchain install stable
|
||||
cd jay
|
||||
git submodule update --init
|
||||
- test: |
|
||||
cd jay
|
||||
cargo test
|
||||
|
|
|
|||
31
.github/workflows/toml-spec.yml
vendored
Normal file
31
.github/workflows/toml-spec.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: toml-spec
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Show env
|
||||
run: |
|
||||
uname -a
|
||||
ldd --version
|
||||
- name: Install
|
||||
run: |
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
|
||||
rustup toolchain install nightly --allow-downgrade -c rustfmt
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: Check
|
||||
run: |
|
||||
cd toml-spec
|
||||
cargo run
|
||||
git diff --exit-code
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
.*
|
||||
!.gitignore
|
||||
!.gitmodules
|
||||
!/.cargo
|
||||
!/.builds
|
||||
!/.github
|
||||
|
|
|
|||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "toml-config/toml-test"]
|
||||
path = toml-config/toml-test
|
||||
url = https://github.com/mahkoh/toml-tests.git
|
||||
245
Cargo.lock
generated
245
Cargo.lock
generated
|
|
@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
|||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.7"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
|
|
@ -77,9 +77,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
|
|
@ -111,9 +111,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
|
|
@ -174,9 +174,9 @@ checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.9.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
|
|
@ -292,13 +292,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "default-config"
|
||||
version = "0.1.0"
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"jay-config",
|
||||
"log",
|
||||
"rand",
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -479,12 +478,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.2"
|
||||
version = "2.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -516,7 +516,6 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"default-config",
|
||||
"dirs",
|
||||
"futures-util",
|
||||
"gpu-alloc",
|
||||
|
|
@ -525,6 +524,7 @@ dependencies = [
|
|||
"indexmap",
|
||||
"isnt",
|
||||
"jay-config",
|
||||
"jay-toml-config",
|
||||
"libloading 0.8.1",
|
||||
"log",
|
||||
"num-derive",
|
||||
|
|
@ -557,6 +557,23 @@ dependencies = [
|
|||
"uapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jay-toml-config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bstr",
|
||||
"error_reporter",
|
||||
"indexmap",
|
||||
"jay-config",
|
||||
"log",
|
||||
"phf",
|
||||
"serde_json",
|
||||
"simplelog",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.67"
|
||||
|
|
@ -627,9 +644,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
|
|
@ -646,6 +663,12 @@ dependencies = [
|
|||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.1"
|
||||
|
|
@ -666,6 +689,15 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
|
|
@ -710,6 +742,48 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.5"
|
||||
|
|
@ -742,6 +816,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
|
|
@ -894,6 +974,15 @@ version = "1.0.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
|
|
@ -902,18 +991,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.196"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.196"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -926,11 +1015,25 @@ version = "1.0.114"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shaderc"
|
||||
version = "0.8.3"
|
||||
|
|
@ -952,6 +1055,23 @@ dependencies = [
|
|||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simplelog"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"termcolor",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
@ -995,6 +1115,15 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.3.0"
|
||||
|
|
@ -1025,6 +1154,39 @@ dependencies = [
|
|||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
|
@ -1040,6 +1202,18 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml-spec"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"error_reporter",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uapi"
|
||||
version = "0.2.13"
|
||||
|
|
@ -1072,6 +1246,12 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
|
|
@ -1084,6 +1264,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
@ -1160,6 +1350,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
build = "build/build.rs"
|
||||
|
||||
[workspace]
|
||||
members = ["jay-config", "default-config", "algorithms"]
|
||||
members = ["jay-config", "toml-config", "algorithms", "toml-spec"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
|
@ -30,7 +30,7 @@ smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "uni
|
|||
byteorder = "1.5.0"
|
||||
bincode = "1.3.3"
|
||||
jay-config = { path = "jay-config" }
|
||||
default-config = { path = "default-config" }
|
||||
jay-toml-config = { path = "toml-config" }
|
||||
algorithms = { path = "algorithms" }
|
||||
pin-project = "1.1.4"
|
||||
clap = { version = "4.4.18", features = ["derive", "wrap_help"] }
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "default-config"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
jay-config = { path = "../jay-config" }
|
||||
log = "0.4.14"
|
||||
rand = "0.8.5"
|
||||
chrono = "0.4.19"
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
use {
|
||||
chrono::{format::StrftimeItems, Local},
|
||||
jay_config::{
|
||||
config,
|
||||
exec::Command,
|
||||
get_workspace,
|
||||
input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat},
|
||||
keyboard::{
|
||||
mods::{Modifiers, ALT, CTRL, SHIFT},
|
||||
syms::{
|
||||
SYM_Super_L, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p, SYM_q,
|
||||
SYM_r, SYM_t, SYM_u, SYM_v, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F2, SYM_F3,
|
||||
SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9,
|
||||
},
|
||||
},
|
||||
quit, reload,
|
||||
status::set_status,
|
||||
switch_to_vt,
|
||||
timer::{duration_until_wall_clock_is_multiple_of, get_timer},
|
||||
video::on_graphics_initialized,
|
||||
Axis::{Horizontal, Vertical},
|
||||
Direction::{Down, Left, Right, Up},
|
||||
},
|
||||
std::time::Duration,
|
||||
};
|
||||
|
||||
const MOD: Modifiers = ALT;
|
||||
|
||||
fn configure_seat(s: Seat) {
|
||||
s.bind(MOD | SYM_h, move || s.focus(Left));
|
||||
s.bind(MOD | SYM_j, move || s.focus(Down));
|
||||
s.bind(MOD | SYM_k, move || s.focus(Up));
|
||||
s.bind(MOD | SYM_l, move || s.focus(Right));
|
||||
|
||||
s.bind(MOD | SHIFT | SYM_h, move || s.move_(Left));
|
||||
s.bind(MOD | SHIFT | SYM_j, move || s.move_(Down));
|
||||
s.bind(MOD | SHIFT | SYM_k, move || s.move_(Up));
|
||||
s.bind(MOD | SHIFT | SYM_l, move || s.move_(Right));
|
||||
|
||||
s.bind(MOD | SYM_d, move || s.create_split(Horizontal));
|
||||
s.bind(MOD | SYM_v, move || s.create_split(Vertical));
|
||||
|
||||
s.bind(MOD | SYM_t, move || s.toggle_split());
|
||||
s.bind(MOD | SYM_m, move || s.toggle_mono());
|
||||
s.bind(MOD | SYM_u, move || s.toggle_fullscreen());
|
||||
|
||||
s.bind(MOD | SYM_f, move || s.focus_parent());
|
||||
|
||||
s.bind(MOD | SHIFT | SYM_c, move || s.close());
|
||||
|
||||
s.bind(MOD | SHIFT | SYM_f, move || s.toggle_floating());
|
||||
|
||||
s.bind(SYM_Super_L, || Command::new("alacritty").spawn());
|
||||
|
||||
s.bind(MOD | SYM_p, || Command::new("bemenu-run").spawn());
|
||||
|
||||
s.bind(MOD | SYM_q, quit);
|
||||
|
||||
s.bind(MOD | SHIFT | SYM_r, reload);
|
||||
|
||||
let fnkeys = [
|
||||
SYM_F1, SYM_F2, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, SYM_F10, SYM_F11,
|
||||
SYM_F12,
|
||||
];
|
||||
for (i, sym) in fnkeys.into_iter().enumerate() {
|
||||
s.bind(CTRL | ALT | sym, move || switch_to_vt(i as u32 + 1));
|
||||
|
||||
let ws = get_workspace(&format!("{}", i + 1));
|
||||
s.bind(MOD | sym, move || s.show_workspace(ws));
|
||||
s.bind(MOD | SHIFT | sym, move || s.set_workspace(ws));
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_status() {
|
||||
let time_format: Vec<_> = StrftimeItems::new("%Y-%m-%d %H:%M:%S").collect();
|
||||
let update_status = move || {
|
||||
let status = format!("{}", Local::now().format_with_items(time_format.iter()));
|
||||
set_status(&status);
|
||||
};
|
||||
update_status();
|
||||
let period = Duration::from_secs(5);
|
||||
let timer = get_timer("status_timer");
|
||||
timer.repeated(duration_until_wall_clock_is_multiple_of(period), period);
|
||||
timer.on_tick(update_status);
|
||||
}
|
||||
|
||||
pub fn configure() {
|
||||
// Configure seats and input devices
|
||||
let seat = get_seat("default");
|
||||
configure_seat(seat);
|
||||
let handle_input_device = move |device: InputDevice| {
|
||||
device.set_seat(seat);
|
||||
};
|
||||
input_devices().into_iter().for_each(handle_input_device);
|
||||
on_new_input_device(handle_input_device);
|
||||
|
||||
// Configure the status message
|
||||
setup_status();
|
||||
|
||||
// Start programs
|
||||
on_graphics_initialized(|| {
|
||||
Command::new("mako").spawn();
|
||||
});
|
||||
}
|
||||
|
||||
config!(configure);
|
||||
|
|
@ -62,3 +62,5 @@ impl WireMode {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct PollableId(pub u64);
|
||||
|
||||
pub const DEFAULT_SEAT_NAME: &str = "default";
|
||||
|
|
|
|||
|
|
@ -506,6 +506,14 @@ impl Client {
|
|||
self.send(&ClientMessage::SetEnv { key, val });
|
||||
}
|
||||
|
||||
pub fn set_log_level(&self, level: LogLevel) {
|
||||
self.send(&ClientMessage::SetLogLevel { level })
|
||||
}
|
||||
|
||||
pub fn unset_env(&self, key: &str) {
|
||||
self.send(&ClientMessage::UnsetEnv { key });
|
||||
}
|
||||
|
||||
pub fn set_status(&self, status: &str) {
|
||||
self.send(&ClientMessage::SetStatus { status });
|
||||
}
|
||||
|
|
@ -576,6 +584,16 @@ impl Client {
|
|||
self.send(&ClientMessage::SetDoubleClickDistance { dist });
|
||||
}
|
||||
|
||||
pub fn disable_default_seat(&self) {
|
||||
self.send(&ClientMessage::DisableDefaultSeat);
|
||||
}
|
||||
|
||||
pub fn connector_get_position(&self, connector: Connector) -> (i32, i32) {
|
||||
let res = self.send_with_response(&ClientMessage::ConnectorGetPosition { connector });
|
||||
get_response!(res, (0, 0), ConnectorGetPosition { x, y });
|
||||
(x, y)
|
||||
}
|
||||
|
||||
pub fn connector_set_position(&self, connector: Connector, x: i32, y: i32) {
|
||||
self.send(&ClientMessage::ConnectorSetPosition { connector, x, y });
|
||||
}
|
||||
|
|
@ -591,9 +609,49 @@ impl Client {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn device_connectors(&self, device: DrmDevice) -> Vec<Connector> {
|
||||
let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device });
|
||||
get_response!(res, vec![], GetDeviceConnectors { connectors });
|
||||
pub fn connector_get_name(&self, connector: Connector) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetConnectorName { connector });
|
||||
get_response!(res, String::new(), GetConnectorName { name });
|
||||
name
|
||||
}
|
||||
|
||||
pub fn connector_get_model(&self, connector: Connector) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetConnectorModel { connector });
|
||||
get_response!(res, String::new(), GetConnectorModel { model });
|
||||
model
|
||||
}
|
||||
|
||||
pub fn connector_get_manufacturer(&self, connector: Connector) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetConnectorManufacturer { connector });
|
||||
get_response!(
|
||||
res,
|
||||
String::new(),
|
||||
GetConnectorManufacturer { manufacturer }
|
||||
);
|
||||
manufacturer
|
||||
}
|
||||
|
||||
pub fn connector_get_serial_number(&self, connector: Connector) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetConnectorSerialNumber { connector });
|
||||
get_response!(
|
||||
res,
|
||||
String::new(),
|
||||
GetConnectorSerialNumber { serial_number }
|
||||
);
|
||||
serial_number
|
||||
}
|
||||
|
||||
pub fn connectors(&self, device: Option<DrmDevice>) -> Vec<Connector> {
|
||||
if let Some(device) = device {
|
||||
let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device });
|
||||
get_response!(res, vec![], GetConnectors { connectors });
|
||||
return connectors;
|
||||
}
|
||||
let res = self.send_with_response(&ClientMessage::GetConnectors {
|
||||
device,
|
||||
connected_only: false,
|
||||
});
|
||||
get_response!(res, vec![], GetConnectors { connectors });
|
||||
connectors
|
||||
}
|
||||
|
||||
|
|
@ -603,6 +661,12 @@ impl Client {
|
|||
syspath
|
||||
}
|
||||
|
||||
pub fn drm_device_devnode(&self, device: DrmDevice) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetDrmDeviceDevnode { device });
|
||||
get_response!(res, String::new(), GetDrmDeviceDevnode { devnode });
|
||||
devnode
|
||||
}
|
||||
|
||||
pub fn drm_device_vendor(&self, device: DrmDevice) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetDrmDeviceVendor { device });
|
||||
get_response!(res, String::new(), GetDrmDeviceVendor { vendor });
|
||||
|
|
@ -723,6 +787,22 @@ impl Client {
|
|||
self.on_devices_enumerated.set(Some(Box::new(f)));
|
||||
}
|
||||
|
||||
pub fn config_dir(&self) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetConfigDir);
|
||||
get_response!(res, String::new(), GetConfigDir { dir });
|
||||
dir
|
||||
}
|
||||
|
||||
pub fn workspaces(&self) -> Vec<Workspace> {
|
||||
let res = self.send_with_response(&ClientMessage::GetWorkspaces);
|
||||
get_response!(res, vec![], GetWorkspaces { workspaces });
|
||||
workspaces
|
||||
}
|
||||
|
||||
pub fn set_idle(&self, timeout: Duration) {
|
||||
self.send(&ClientMessage::SetIdle { timeout })
|
||||
}
|
||||
|
||||
pub fn set_seat(&self, device: InputDevice, seat: Seat) {
|
||||
self.send(&ClientMessage::SetSeat { device, seat })
|
||||
}
|
||||
|
|
@ -772,12 +852,28 @@ impl Client {
|
|||
name
|
||||
}
|
||||
|
||||
pub fn input_device_syspath(&self, device: InputDevice) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetInputDeviceSyspath { device });
|
||||
get_response!(res, String::new(), GetInputDeviceSyspath { syspath });
|
||||
syspath
|
||||
}
|
||||
|
||||
pub fn input_device_devnode(&self, device: InputDevice) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetInputDeviceDevnode { device });
|
||||
get_response!(res, String::new(), GetInputDeviceDevnode { devnode });
|
||||
devnode
|
||||
}
|
||||
|
||||
pub fn has_capability(&self, device: InputDevice, cap: Capability) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::HasCapability { device, cap });
|
||||
get_response!(res, false, HasCapability { has });
|
||||
has
|
||||
}
|
||||
|
||||
pub fn destroy_keymap(&self, keymap: Keymap) {
|
||||
self.send(&ClientMessage::DestroyKeymap { keymap })
|
||||
}
|
||||
|
||||
pub fn seat_set_keymap(&self, seat: Seat, keymap: Keymap) {
|
||||
self.send(&ClientMessage::SeatSetKeymap { seat, keymap })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -381,6 +381,49 @@ pub enum ClientMessage<'a> {
|
|||
env: Vec<(String, String)>,
|
||||
fds: Vec<(i32, i32)>,
|
||||
},
|
||||
DisableDefaultSeat,
|
||||
DestroyKeymap {
|
||||
keymap: Keymap,
|
||||
},
|
||||
GetConnectorName {
|
||||
connector: Connector,
|
||||
},
|
||||
GetConnectorModel {
|
||||
connector: Connector,
|
||||
},
|
||||
GetConnectorManufacturer {
|
||||
connector: Connector,
|
||||
},
|
||||
GetConnectorSerialNumber {
|
||||
connector: Connector,
|
||||
},
|
||||
GetConnectors {
|
||||
device: Option<DrmDevice>,
|
||||
connected_only: bool,
|
||||
},
|
||||
ConnectorGetPosition {
|
||||
connector: Connector,
|
||||
},
|
||||
GetConfigDir,
|
||||
GetWorkspaces,
|
||||
UnsetEnv {
|
||||
key: &'a str,
|
||||
},
|
||||
SetLogLevel {
|
||||
level: LogLevel,
|
||||
},
|
||||
GetDrmDeviceDevnode {
|
||||
device: DrmDevice,
|
||||
},
|
||||
GetInputDeviceSyspath {
|
||||
device: InputDevice,
|
||||
},
|
||||
GetInputDeviceDevnode {
|
||||
device: InputDevice,
|
||||
},
|
||||
SetIdle {
|
||||
timeout: Duration,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -440,7 +483,7 @@ pub enum Response {
|
|||
GetFullscreen {
|
||||
fullscreen: bool,
|
||||
},
|
||||
GetDeviceConnectors {
|
||||
GetConnectors {
|
||||
connectors: Vec<Connector>,
|
||||
},
|
||||
GetDrmDeviceSyspath {
|
||||
|
|
@ -489,6 +532,37 @@ pub enum Response {
|
|||
AddPollable {
|
||||
id: Result<PollableId, String>,
|
||||
},
|
||||
GetConnectorName {
|
||||
name: String,
|
||||
},
|
||||
GetConnectorModel {
|
||||
model: String,
|
||||
},
|
||||
GetConnectorManufacturer {
|
||||
manufacturer: String,
|
||||
},
|
||||
GetConnectorSerialNumber {
|
||||
serial_number: String,
|
||||
},
|
||||
ConnectorGetPosition {
|
||||
x: i32,
|
||||
y: i32,
|
||||
},
|
||||
GetConfigDir {
|
||||
dir: String,
|
||||
},
|
||||
GetWorkspaces {
|
||||
workspaces: Vec<Workspace>,
|
||||
},
|
||||
GetDrmDeviceDevnode {
|
||||
devnode: String,
|
||||
},
|
||||
GetInputDeviceSyspath {
|
||||
syspath: String,
|
||||
},
|
||||
GetInputDeviceDevnode {
|
||||
devnode: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ pub fn set_env(key: &str, val: &str) {
|
|||
get!().set_env(key, val);
|
||||
}
|
||||
|
||||
/// Unsets an environment variable.
|
||||
///
|
||||
/// This does not affect the compositor itself but only programs spawned by the compositor.
|
||||
pub fn unset_env(key: &str) {
|
||||
get!().unset_env(key);
|
||||
}
|
||||
|
||||
/// A command to be spawned.
|
||||
pub struct Command {
|
||||
pub(crate) prog: String,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use {
|
|||
input::{acceleration::AccelProfile, capability::Capability},
|
||||
keyboard::Keymap,
|
||||
Axis, Direction, ModifiedKeySym, Workspace,
|
||||
_private::DEFAULT_SEAT_NAME,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::time::Duration,
|
||||
|
|
@ -112,6 +113,20 @@ impl InputDevice {
|
|||
pub fn set_natural_scrolling_enabled(self, enabled: bool) {
|
||||
get!().set_input_natural_scrolling_enabled(self, enabled);
|
||||
}
|
||||
|
||||
/// Returns the syspath of this device.
|
||||
///
|
||||
/// E.g. `/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.3/5-1.1.3:1.0`.
|
||||
pub fn syspath(self) -> String {
|
||||
get!(String::new()).input_device_syspath(self)
|
||||
}
|
||||
|
||||
/// Returns the devnode of this device.
|
||||
///
|
||||
/// E.g. `/dev/input/event7`.
|
||||
pub fn devnode(self) -> String {
|
||||
get!(String::new()).input_device_devnode(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A seat.
|
||||
|
|
@ -319,10 +334,20 @@ pub fn input_devices() -> Vec<InputDevice> {
|
|||
/// Returns or creates a seat.
|
||||
///
|
||||
/// Seats are identified by their name. If no seat with the name exists, a new seat will be created.
|
||||
///
|
||||
/// NOTE: You should prefer [`get_default_seat`] instead. Most applications cannot handle more than
|
||||
/// one seat and will only process input from one of the seats.
|
||||
pub fn get_seat(name: &str) -> Seat {
|
||||
get!(Seat(0)).get_seat(name)
|
||||
}
|
||||
|
||||
/// Returns or creates the default seat.
|
||||
///
|
||||
/// This is equivalent to `get_seat("default")`.
|
||||
pub fn get_default_seat() -> Seat {
|
||||
get_seat(DEFAULT_SEAT_NAME)
|
||||
}
|
||||
|
||||
/// Sets a closure to run when a new seat has been created.
|
||||
pub fn on_new_seat<F: FnMut(Seat) + 'static>(f: F) {
|
||||
get!().on_new_seat(f)
|
||||
|
|
@ -357,3 +382,14 @@ pub fn set_double_click_time(duration: Duration) {
|
|||
pub fn set_double_click_distance(distance: i32) {
|
||||
get!().set_double_click_distance(distance)
|
||||
}
|
||||
|
||||
/// Disables the creation of a default seat.
|
||||
///
|
||||
/// Unless this function is called at startup of the compositor, a seat called `default`
|
||||
/// will automatically be created.
|
||||
///
|
||||
/// When a new input device is attached and a seat called `default` exists, the input
|
||||
/// device is initially attached to this seat.
|
||||
pub fn disable_default_seat() {
|
||||
get!().disable_default_seat();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,15 @@ impl Keymap {
|
|||
pub fn is_invalid(self) -> bool {
|
||||
self == Self::INVALID
|
||||
}
|
||||
|
||||
/// Destroys this reference to the keymap.
|
||||
///
|
||||
/// Seats that are currently using this keymap are unaffected.
|
||||
pub fn destroy(self) {
|
||||
if self.is_valid() {
|
||||
get!().destroy_keymap(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a keymap.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize};
|
|||
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub struct KeySym(pub u32);
|
||||
|
||||
pub const SYM_NoSymbol: KeySym = KeySym(0x000000);
|
||||
pub const SYM_VoidSymbol: KeySym = KeySym(0xffffff);
|
||||
pub const SYM_BackSpace: KeySym = KeySym(0xff08);
|
||||
pub const SYM_Tab: KeySym = KeySym(0xff09);
|
||||
pub const SYM_Linefeed: KeySym = KeySym(0xff0a);
|
||||
|
|
@ -269,8 +271,11 @@ pub const SYM_dead_O: KeySym = KeySym(0xfe87);
|
|||
pub const SYM_dead_u: KeySym = KeySym(0xfe88);
|
||||
pub const SYM_dead_U: KeySym = KeySym(0xfe89);
|
||||
pub const SYM_dead_small_schwa: KeySym = KeySym(0xfe8a);
|
||||
pub const SYM_dead_schwa: KeySym = KeySym(0xfe8a);
|
||||
pub const SYM_dead_capital_schwa: KeySym = KeySym(0xfe8b);
|
||||
pub const SYM_dead_SCHWA: KeySym = KeySym(0xfe8b);
|
||||
pub const SYM_dead_greek: KeySym = KeySym(0xfe8c);
|
||||
pub const SYM_dead_hamza: KeySym = KeySym(0xfe8d);
|
||||
pub const SYM_First_Virtual_Screen: KeySym = KeySym(0xfed0);
|
||||
pub const SYM_Prev_Virtual_Screen: KeySym = KeySym(0xfed1);
|
||||
pub const SYM_Next_Virtual_Screen: KeySym = KeySym(0xfed2);
|
||||
|
|
@ -462,6 +467,7 @@ pub const SYM_diaeresis: KeySym = KeySym(0x00a8);
|
|||
pub const SYM_copyright: KeySym = KeySym(0x00a9);
|
||||
pub const SYM_ordfeminine: KeySym = KeySym(0x00aa);
|
||||
pub const SYM_guillemotleft: KeySym = KeySym(0x00ab);
|
||||
pub const SYM_guillemetleft: KeySym = KeySym(0x00ab);
|
||||
pub const SYM_notsign: KeySym = KeySym(0x00ac);
|
||||
pub const SYM_hyphen: KeySym = KeySym(0x00ad);
|
||||
pub const SYM_registered: KeySym = KeySym(0x00ae);
|
||||
|
|
@ -477,7 +483,9 @@ pub const SYM_periodcentered: KeySym = KeySym(0x00b7);
|
|||
pub const SYM_cedilla: KeySym = KeySym(0x00b8);
|
||||
pub const SYM_onesuperior: KeySym = KeySym(0x00b9);
|
||||
pub const SYM_masculine: KeySym = KeySym(0x00ba);
|
||||
pub const SYM_ordmasculine: KeySym = KeySym(0x00ba);
|
||||
pub const SYM_guillemotright: KeySym = KeySym(0x00bb);
|
||||
pub const SYM_guillemetright: KeySym = KeySym(0x00bb);
|
||||
pub const SYM_onequarter: KeySym = KeySym(0x00bc);
|
||||
pub const SYM_onehalf: KeySym = KeySym(0x00bd);
|
||||
pub const SYM_threequarters: KeySym = KeySym(0x00be);
|
||||
|
|
@ -1754,12 +1762,12 @@ pub const SYM_partdifferential: KeySym = KeySym(0x1002202);
|
|||
pub const SYM_emptyset: KeySym = KeySym(0x1002205);
|
||||
pub const SYM_elementof: KeySym = KeySym(0x1002208);
|
||||
pub const SYM_notelementof: KeySym = KeySym(0x1002209);
|
||||
pub const SYM_containsas: KeySym = KeySym(0x100220B);
|
||||
pub const SYM_squareroot: KeySym = KeySym(0x100221A);
|
||||
pub const SYM_cuberoot: KeySym = KeySym(0x100221B);
|
||||
pub const SYM_fourthroot: KeySym = KeySym(0x100221C);
|
||||
pub const SYM_dintegral: KeySym = KeySym(0x100222C);
|
||||
pub const SYM_tintegral: KeySym = KeySym(0x100222D);
|
||||
pub const SYM_containsas: KeySym = KeySym(0x100220b);
|
||||
pub const SYM_squareroot: KeySym = KeySym(0x100221a);
|
||||
pub const SYM_cuberoot: KeySym = KeySym(0x100221b);
|
||||
pub const SYM_fourthroot: KeySym = KeySym(0x100221c);
|
||||
pub const SYM_dintegral: KeySym = KeySym(0x100222c);
|
||||
pub const SYM_tintegral: KeySym = KeySym(0x100222d);
|
||||
pub const SYM_because: KeySym = KeySym(0x1002235);
|
||||
pub const SYM_approxeq: KeySym = KeySym(0x1002248);
|
||||
pub const SYM_notapproxeq: KeySym = KeySym(0x1002247);
|
||||
|
|
@ -2111,190 +2119,190 @@ pub const SYM_Sinh_lu2: KeySym = KeySym(0x1000ddf);
|
|||
pub const SYM_Sinh_ruu2: KeySym = KeySym(0x1000df2);
|
||||
pub const SYM_Sinh_luu2: KeySym = KeySym(0x1000df3);
|
||||
pub const SYM_Sinh_kunddaliya: KeySym = KeySym(0x1000df4);
|
||||
pub const SYM_XF86ModeLock: KeySym = KeySym(0x1008FF01);
|
||||
pub const SYM_XF86MonBrightnessUp: KeySym = KeySym(0x1008FF02);
|
||||
pub const SYM_XF86MonBrightnessDown: KeySym = KeySym(0x1008FF03);
|
||||
pub const SYM_XF86KbdLightOnOff: KeySym = KeySym(0x1008FF04);
|
||||
pub const SYM_XF86KbdBrightnessUp: KeySym = KeySym(0x1008FF05);
|
||||
pub const SYM_XF86KbdBrightnessDown: KeySym = KeySym(0x1008FF06);
|
||||
pub const SYM_XF86MonBrightnessCycle: KeySym = KeySym(0x1008FF07);
|
||||
pub const SYM_XF86Standby: KeySym = KeySym(0x1008FF10);
|
||||
pub const SYM_XF86AudioLowerVolume: KeySym = KeySym(0x1008FF11);
|
||||
pub const SYM_XF86AudioMute: KeySym = KeySym(0x1008FF12);
|
||||
pub const SYM_XF86AudioRaiseVolume: KeySym = KeySym(0x1008FF13);
|
||||
pub const SYM_XF86AudioPlay: KeySym = KeySym(0x1008FF14);
|
||||
pub const SYM_XF86AudioStop: KeySym = KeySym(0x1008FF15);
|
||||
pub const SYM_XF86AudioPrev: KeySym = KeySym(0x1008FF16);
|
||||
pub const SYM_XF86AudioNext: KeySym = KeySym(0x1008FF17);
|
||||
pub const SYM_XF86HomePage: KeySym = KeySym(0x1008FF18);
|
||||
pub const SYM_XF86Mail: KeySym = KeySym(0x1008FF19);
|
||||
pub const SYM_XF86Start: KeySym = KeySym(0x1008FF1A);
|
||||
pub const SYM_XF86Search: KeySym = KeySym(0x1008FF1B);
|
||||
pub const SYM_XF86AudioRecord: KeySym = KeySym(0x1008FF1C);
|
||||
pub const SYM_XF86Calculator: KeySym = KeySym(0x1008FF1D);
|
||||
pub const SYM_XF86Memo: KeySym = KeySym(0x1008FF1E);
|
||||
pub const SYM_XF86ToDoList: KeySym = KeySym(0x1008FF1F);
|
||||
pub const SYM_XF86Calendar: KeySym = KeySym(0x1008FF20);
|
||||
pub const SYM_XF86PowerDown: KeySym = KeySym(0x1008FF21);
|
||||
pub const SYM_XF86ContrastAdjust: KeySym = KeySym(0x1008FF22);
|
||||
pub const SYM_XF86RockerUp: KeySym = KeySym(0x1008FF23);
|
||||
pub const SYM_XF86RockerDown: KeySym = KeySym(0x1008FF24);
|
||||
pub const SYM_XF86RockerEnter: KeySym = KeySym(0x1008FF25);
|
||||
pub const SYM_XF86Back: KeySym = KeySym(0x1008FF26);
|
||||
pub const SYM_XF86Forward: KeySym = KeySym(0x1008FF27);
|
||||
pub const SYM_XF86Stop: KeySym = KeySym(0x1008FF28);
|
||||
pub const SYM_XF86Refresh: KeySym = KeySym(0x1008FF29);
|
||||
pub const SYM_XF86PowerOff: KeySym = KeySym(0x1008FF2A);
|
||||
pub const SYM_XF86WakeUp: KeySym = KeySym(0x1008FF2B);
|
||||
pub const SYM_XF86Eject: KeySym = KeySym(0x1008FF2C);
|
||||
pub const SYM_XF86ScreenSaver: KeySym = KeySym(0x1008FF2D);
|
||||
pub const SYM_XF86WWW: KeySym = KeySym(0x1008FF2E);
|
||||
pub const SYM_XF86Sleep: KeySym = KeySym(0x1008FF2F);
|
||||
pub const SYM_XF86Favorites: KeySym = KeySym(0x1008FF30);
|
||||
pub const SYM_XF86AudioPause: KeySym = KeySym(0x1008FF31);
|
||||
pub const SYM_XF86AudioMedia: KeySym = KeySym(0x1008FF32);
|
||||
pub const SYM_XF86MyComputer: KeySym = KeySym(0x1008FF33);
|
||||
pub const SYM_XF86VendorHome: KeySym = KeySym(0x1008FF34);
|
||||
pub const SYM_XF86LightBulb: KeySym = KeySym(0x1008FF35);
|
||||
pub const SYM_XF86Shop: KeySym = KeySym(0x1008FF36);
|
||||
pub const SYM_XF86History: KeySym = KeySym(0x1008FF37);
|
||||
pub const SYM_XF86OpenURL: KeySym = KeySym(0x1008FF38);
|
||||
pub const SYM_XF86AddFavorite: KeySym = KeySym(0x1008FF39);
|
||||
pub const SYM_XF86HotLinks: KeySym = KeySym(0x1008FF3A);
|
||||
pub const SYM_XF86BrightnessAdjust: KeySym = KeySym(0x1008FF3B);
|
||||
pub const SYM_XF86Finance: KeySym = KeySym(0x1008FF3C);
|
||||
pub const SYM_XF86Community: KeySym = KeySym(0x1008FF3D);
|
||||
pub const SYM_XF86AudioRewind: KeySym = KeySym(0x1008FF3E);
|
||||
pub const SYM_XF86BackForward: KeySym = KeySym(0x1008FF3F);
|
||||
pub const SYM_XF86Launch0: KeySym = KeySym(0x1008FF40);
|
||||
pub const SYM_XF86Launch1: KeySym = KeySym(0x1008FF41);
|
||||
pub const SYM_XF86Launch2: KeySym = KeySym(0x1008FF42);
|
||||
pub const SYM_XF86Launch3: KeySym = KeySym(0x1008FF43);
|
||||
pub const SYM_XF86Launch4: KeySym = KeySym(0x1008FF44);
|
||||
pub const SYM_XF86Launch5: KeySym = KeySym(0x1008FF45);
|
||||
pub const SYM_XF86Launch6: KeySym = KeySym(0x1008FF46);
|
||||
pub const SYM_XF86Launch7: KeySym = KeySym(0x1008FF47);
|
||||
pub const SYM_XF86Launch8: KeySym = KeySym(0x1008FF48);
|
||||
pub const SYM_XF86Launch9: KeySym = KeySym(0x1008FF49);
|
||||
pub const SYM_XF86LaunchA: KeySym = KeySym(0x1008FF4A);
|
||||
pub const SYM_XF86LaunchB: KeySym = KeySym(0x1008FF4B);
|
||||
pub const SYM_XF86LaunchC: KeySym = KeySym(0x1008FF4C);
|
||||
pub const SYM_XF86LaunchD: KeySym = KeySym(0x1008FF4D);
|
||||
pub const SYM_XF86LaunchE: KeySym = KeySym(0x1008FF4E);
|
||||
pub const SYM_XF86LaunchF: KeySym = KeySym(0x1008FF4F);
|
||||
pub const SYM_XF86ApplicationLeft: KeySym = KeySym(0x1008FF50);
|
||||
pub const SYM_XF86ApplicationRight: KeySym = KeySym(0x1008FF51);
|
||||
pub const SYM_XF86Book: KeySym = KeySym(0x1008FF52);
|
||||
pub const SYM_XF86CD: KeySym = KeySym(0x1008FF53);
|
||||
pub const SYM_XF86Calculater: KeySym = KeySym(0x1008FF54);
|
||||
pub const SYM_XF86Clear: KeySym = KeySym(0x1008FF55);
|
||||
pub const SYM_XF86Close: KeySym = KeySym(0x1008FF56);
|
||||
pub const SYM_XF86Copy: KeySym = KeySym(0x1008FF57);
|
||||
pub const SYM_XF86Cut: KeySym = KeySym(0x1008FF58);
|
||||
pub const SYM_XF86Display: KeySym = KeySym(0x1008FF59);
|
||||
pub const SYM_XF86DOS: KeySym = KeySym(0x1008FF5A);
|
||||
pub const SYM_XF86Documents: KeySym = KeySym(0x1008FF5B);
|
||||
pub const SYM_XF86Excel: KeySym = KeySym(0x1008FF5C);
|
||||
pub const SYM_XF86Explorer: KeySym = KeySym(0x1008FF5D);
|
||||
pub const SYM_XF86Game: KeySym = KeySym(0x1008FF5E);
|
||||
pub const SYM_XF86Go: KeySym = KeySym(0x1008FF5F);
|
||||
pub const SYM_XF86iTouch: KeySym = KeySym(0x1008FF60);
|
||||
pub const SYM_XF86LogOff: KeySym = KeySym(0x1008FF61);
|
||||
pub const SYM_XF86Market: KeySym = KeySym(0x1008FF62);
|
||||
pub const SYM_XF86Meeting: KeySym = KeySym(0x1008FF63);
|
||||
pub const SYM_XF86MenuKB: KeySym = KeySym(0x1008FF65);
|
||||
pub const SYM_XF86MenuPB: KeySym = KeySym(0x1008FF66);
|
||||
pub const SYM_XF86MySites: KeySym = KeySym(0x1008FF67);
|
||||
pub const SYM_XF86New: KeySym = KeySym(0x1008FF68);
|
||||
pub const SYM_XF86News: KeySym = KeySym(0x1008FF69);
|
||||
pub const SYM_XF86OfficeHome: KeySym = KeySym(0x1008FF6A);
|
||||
pub const SYM_XF86Open: KeySym = KeySym(0x1008FF6B);
|
||||
pub const SYM_XF86Option: KeySym = KeySym(0x1008FF6C);
|
||||
pub const SYM_XF86Paste: KeySym = KeySym(0x1008FF6D);
|
||||
pub const SYM_XF86Phone: KeySym = KeySym(0x1008FF6E);
|
||||
pub const SYM_XF86Q: KeySym = KeySym(0x1008FF70);
|
||||
pub const SYM_XF86Reply: KeySym = KeySym(0x1008FF72);
|
||||
pub const SYM_XF86Reload: KeySym = KeySym(0x1008FF73);
|
||||
pub const SYM_XF86RotateWindows: KeySym = KeySym(0x1008FF74);
|
||||
pub const SYM_XF86RotationPB: KeySym = KeySym(0x1008FF75);
|
||||
pub const SYM_XF86RotationKB: KeySym = KeySym(0x1008FF76);
|
||||
pub const SYM_XF86Save: KeySym = KeySym(0x1008FF77);
|
||||
pub const SYM_XF86ScrollUp: KeySym = KeySym(0x1008FF78);
|
||||
pub const SYM_XF86ScrollDown: KeySym = KeySym(0x1008FF79);
|
||||
pub const SYM_XF86ScrollClick: KeySym = KeySym(0x1008FF7A);
|
||||
pub const SYM_XF86Send: KeySym = KeySym(0x1008FF7B);
|
||||
pub const SYM_XF86Spell: KeySym = KeySym(0x1008FF7C);
|
||||
pub const SYM_XF86SplitScreen: KeySym = KeySym(0x1008FF7D);
|
||||
pub const SYM_XF86Support: KeySym = KeySym(0x1008FF7E);
|
||||
pub const SYM_XF86TaskPane: KeySym = KeySym(0x1008FF7F);
|
||||
pub const SYM_XF86Terminal: KeySym = KeySym(0x1008FF80);
|
||||
pub const SYM_XF86Tools: KeySym = KeySym(0x1008FF81);
|
||||
pub const SYM_XF86Travel: KeySym = KeySym(0x1008FF82);
|
||||
pub const SYM_XF86UserPB: KeySym = KeySym(0x1008FF84);
|
||||
pub const SYM_XF86User1KB: KeySym = KeySym(0x1008FF85);
|
||||
pub const SYM_XF86User2KB: KeySym = KeySym(0x1008FF86);
|
||||
pub const SYM_XF86Video: KeySym = KeySym(0x1008FF87);
|
||||
pub const SYM_XF86WheelButton: KeySym = KeySym(0x1008FF88);
|
||||
pub const SYM_XF86Word: KeySym = KeySym(0x1008FF89);
|
||||
pub const SYM_XF86Xfer: KeySym = KeySym(0x1008FF8A);
|
||||
pub const SYM_XF86ZoomIn: KeySym = KeySym(0x1008FF8B);
|
||||
pub const SYM_XF86ZoomOut: KeySym = KeySym(0x1008FF8C);
|
||||
pub const SYM_XF86Away: KeySym = KeySym(0x1008FF8D);
|
||||
pub const SYM_XF86Messenger: KeySym = KeySym(0x1008FF8E);
|
||||
pub const SYM_XF86WebCam: KeySym = KeySym(0x1008FF8F);
|
||||
pub const SYM_XF86MailForward: KeySym = KeySym(0x1008FF90);
|
||||
pub const SYM_XF86Pictures: KeySym = KeySym(0x1008FF91);
|
||||
pub const SYM_XF86Music: KeySym = KeySym(0x1008FF92);
|
||||
pub const SYM_XF86Battery: KeySym = KeySym(0x1008FF93);
|
||||
pub const SYM_XF86Bluetooth: KeySym = KeySym(0x1008FF94);
|
||||
pub const SYM_XF86WLAN: KeySym = KeySym(0x1008FF95);
|
||||
pub const SYM_XF86UWB: KeySym = KeySym(0x1008FF96);
|
||||
pub const SYM_XF86AudioForward: KeySym = KeySym(0x1008FF97);
|
||||
pub const SYM_XF86AudioRepeat: KeySym = KeySym(0x1008FF98);
|
||||
pub const SYM_XF86AudioRandomPlay: KeySym = KeySym(0x1008FF99);
|
||||
pub const SYM_XF86Subtitle: KeySym = KeySym(0x1008FF9A);
|
||||
pub const SYM_XF86AudioCycleTrack: KeySym = KeySym(0x1008FF9B);
|
||||
pub const SYM_XF86CycleAngle: KeySym = KeySym(0x1008FF9C);
|
||||
pub const SYM_XF86FrameBack: KeySym = KeySym(0x1008FF9D);
|
||||
pub const SYM_XF86FrameForward: KeySym = KeySym(0x1008FF9E);
|
||||
pub const SYM_XF86Time: KeySym = KeySym(0x1008FF9F);
|
||||
pub const SYM_XF86Select: KeySym = KeySym(0x1008FFA0);
|
||||
pub const SYM_XF86View: KeySym = KeySym(0x1008FFA1);
|
||||
pub const SYM_XF86TopMenu: KeySym = KeySym(0x1008FFA2);
|
||||
pub const SYM_XF86Red: KeySym = KeySym(0x1008FFA3);
|
||||
pub const SYM_XF86Green: KeySym = KeySym(0x1008FFA4);
|
||||
pub const SYM_XF86Yellow: KeySym = KeySym(0x1008FFA5);
|
||||
pub const SYM_XF86Blue: KeySym = KeySym(0x1008FFA6);
|
||||
pub const SYM_XF86Suspend: KeySym = KeySym(0x1008FFA7);
|
||||
pub const SYM_XF86Hibernate: KeySym = KeySym(0x1008FFA8);
|
||||
pub const SYM_XF86TouchpadToggle: KeySym = KeySym(0x1008FFA9);
|
||||
pub const SYM_XF86TouchpadOn: KeySym = KeySym(0x1008FFB0);
|
||||
pub const SYM_XF86TouchpadOff: KeySym = KeySym(0x1008FFB1);
|
||||
pub const SYM_XF86AudioMicMute: KeySym = KeySym(0x1008FFB2);
|
||||
pub const SYM_XF86Keyboard: KeySym = KeySym(0x1008FFB3);
|
||||
pub const SYM_XF86WWAN: KeySym = KeySym(0x1008FFB4);
|
||||
pub const SYM_XF86RFKill: KeySym = KeySym(0x1008FFB5);
|
||||
pub const SYM_XF86AudioPreset: KeySym = KeySym(0x1008FFB6);
|
||||
pub const SYM_XF86RotationLockToggle: KeySym = KeySym(0x1008FFB7);
|
||||
pub const SYM_XF86FullScreen: KeySym = KeySym(0x1008FFB8);
|
||||
pub const SYM_XF86Switch_VT_1: KeySym = KeySym(0x1008FE01);
|
||||
pub const SYM_XF86Switch_VT_2: KeySym = KeySym(0x1008FE02);
|
||||
pub const SYM_XF86Switch_VT_3: KeySym = KeySym(0x1008FE03);
|
||||
pub const SYM_XF86Switch_VT_4: KeySym = KeySym(0x1008FE04);
|
||||
pub const SYM_XF86Switch_VT_5: KeySym = KeySym(0x1008FE05);
|
||||
pub const SYM_XF86Switch_VT_6: KeySym = KeySym(0x1008FE06);
|
||||
pub const SYM_XF86Switch_VT_7: KeySym = KeySym(0x1008FE07);
|
||||
pub const SYM_XF86Switch_VT_8: KeySym = KeySym(0x1008FE08);
|
||||
pub const SYM_XF86Switch_VT_9: KeySym = KeySym(0x1008FE09);
|
||||
pub const SYM_XF86Switch_VT_10: KeySym = KeySym(0x1008FE0A);
|
||||
pub const SYM_XF86Switch_VT_11: KeySym = KeySym(0x1008FE0B);
|
||||
pub const SYM_XF86Switch_VT_12: KeySym = KeySym(0x1008FE0C);
|
||||
pub const SYM_XF86Ungrab: KeySym = KeySym(0x1008FE20);
|
||||
pub const SYM_XF86ClearGrab: KeySym = KeySym(0x1008FE21);
|
||||
pub const SYM_XF86Next_VMode: KeySym = KeySym(0x1008FE22);
|
||||
pub const SYM_XF86Prev_VMode: KeySym = KeySym(0x1008FE23);
|
||||
pub const SYM_XF86LogWindowTree: KeySym = KeySym(0x1008FE24);
|
||||
pub const SYM_XF86LogGrabInfo: KeySym = KeySym(0x1008FE25);
|
||||
pub const SYM_XF86ModeLock: KeySym = KeySym(0x1008ff01);
|
||||
pub const SYM_XF86MonBrightnessUp: KeySym = KeySym(0x1008ff02);
|
||||
pub const SYM_XF86MonBrightnessDown: KeySym = KeySym(0x1008ff03);
|
||||
pub const SYM_XF86KbdLightOnOff: KeySym = KeySym(0x1008ff04);
|
||||
pub const SYM_XF86KbdBrightnessUp: KeySym = KeySym(0x1008ff05);
|
||||
pub const SYM_XF86KbdBrightnessDown: KeySym = KeySym(0x1008ff06);
|
||||
pub const SYM_XF86MonBrightnessCycle: KeySym = KeySym(0x1008ff07);
|
||||
pub const SYM_XF86Standby: KeySym = KeySym(0x1008ff10);
|
||||
pub const SYM_XF86AudioLowerVolume: KeySym = KeySym(0x1008ff11);
|
||||
pub const SYM_XF86AudioMute: KeySym = KeySym(0x1008ff12);
|
||||
pub const SYM_XF86AudioRaiseVolume: KeySym = KeySym(0x1008ff13);
|
||||
pub const SYM_XF86AudioPlay: KeySym = KeySym(0x1008ff14);
|
||||
pub const SYM_XF86AudioStop: KeySym = KeySym(0x1008ff15);
|
||||
pub const SYM_XF86AudioPrev: KeySym = KeySym(0x1008ff16);
|
||||
pub const SYM_XF86AudioNext: KeySym = KeySym(0x1008ff17);
|
||||
pub const SYM_XF86HomePage: KeySym = KeySym(0x1008ff18);
|
||||
pub const SYM_XF86Mail: KeySym = KeySym(0x1008ff19);
|
||||
pub const SYM_XF86Start: KeySym = KeySym(0x1008ff1a);
|
||||
pub const SYM_XF86Search: KeySym = KeySym(0x1008ff1b);
|
||||
pub const SYM_XF86AudioRecord: KeySym = KeySym(0x1008ff1c);
|
||||
pub const SYM_XF86Calculator: KeySym = KeySym(0x1008ff1d);
|
||||
pub const SYM_XF86Memo: KeySym = KeySym(0x1008ff1e);
|
||||
pub const SYM_XF86ToDoList: KeySym = KeySym(0x1008ff1f);
|
||||
pub const SYM_XF86Calendar: KeySym = KeySym(0x1008ff20);
|
||||
pub const SYM_XF86PowerDown: KeySym = KeySym(0x1008ff21);
|
||||
pub const SYM_XF86ContrastAdjust: KeySym = KeySym(0x1008ff22);
|
||||
pub const SYM_XF86RockerUp: KeySym = KeySym(0x1008ff23);
|
||||
pub const SYM_XF86RockerDown: KeySym = KeySym(0x1008ff24);
|
||||
pub const SYM_XF86RockerEnter: KeySym = KeySym(0x1008ff25);
|
||||
pub const SYM_XF86Back: KeySym = KeySym(0x1008ff26);
|
||||
pub const SYM_XF86Forward: KeySym = KeySym(0x1008ff27);
|
||||
pub const SYM_XF86Stop: KeySym = KeySym(0x1008ff28);
|
||||
pub const SYM_XF86Refresh: KeySym = KeySym(0x1008ff29);
|
||||
pub const SYM_XF86PowerOff: KeySym = KeySym(0x1008ff2a);
|
||||
pub const SYM_XF86WakeUp: KeySym = KeySym(0x1008ff2b);
|
||||
pub const SYM_XF86Eject: KeySym = KeySym(0x1008ff2c);
|
||||
pub const SYM_XF86ScreenSaver: KeySym = KeySym(0x1008ff2d);
|
||||
pub const SYM_XF86WWW: KeySym = KeySym(0x1008ff2e);
|
||||
pub const SYM_XF86Sleep: KeySym = KeySym(0x1008ff2f);
|
||||
pub const SYM_XF86Favorites: KeySym = KeySym(0x1008ff30);
|
||||
pub const SYM_XF86AudioPause: KeySym = KeySym(0x1008ff31);
|
||||
pub const SYM_XF86AudioMedia: KeySym = KeySym(0x1008ff32);
|
||||
pub const SYM_XF86MyComputer: KeySym = KeySym(0x1008ff33);
|
||||
pub const SYM_XF86VendorHome: KeySym = KeySym(0x1008ff34);
|
||||
pub const SYM_XF86LightBulb: KeySym = KeySym(0x1008ff35);
|
||||
pub const SYM_XF86Shop: KeySym = KeySym(0x1008ff36);
|
||||
pub const SYM_XF86History: KeySym = KeySym(0x1008ff37);
|
||||
pub const SYM_XF86OpenURL: KeySym = KeySym(0x1008ff38);
|
||||
pub const SYM_XF86AddFavorite: KeySym = KeySym(0x1008ff39);
|
||||
pub const SYM_XF86HotLinks: KeySym = KeySym(0x1008ff3a);
|
||||
pub const SYM_XF86BrightnessAdjust: KeySym = KeySym(0x1008ff3b);
|
||||
pub const SYM_XF86Finance: KeySym = KeySym(0x1008ff3c);
|
||||
pub const SYM_XF86Community: KeySym = KeySym(0x1008ff3d);
|
||||
pub const SYM_XF86AudioRewind: KeySym = KeySym(0x1008ff3e);
|
||||
pub const SYM_XF86BackForward: KeySym = KeySym(0x1008ff3f);
|
||||
pub const SYM_XF86Launch0: KeySym = KeySym(0x1008ff40);
|
||||
pub const SYM_XF86Launch1: KeySym = KeySym(0x1008ff41);
|
||||
pub const SYM_XF86Launch2: KeySym = KeySym(0x1008ff42);
|
||||
pub const SYM_XF86Launch3: KeySym = KeySym(0x1008ff43);
|
||||
pub const SYM_XF86Launch4: KeySym = KeySym(0x1008ff44);
|
||||
pub const SYM_XF86Launch5: KeySym = KeySym(0x1008ff45);
|
||||
pub const SYM_XF86Launch6: KeySym = KeySym(0x1008ff46);
|
||||
pub const SYM_XF86Launch7: KeySym = KeySym(0x1008ff47);
|
||||
pub const SYM_XF86Launch8: KeySym = KeySym(0x1008ff48);
|
||||
pub const SYM_XF86Launch9: KeySym = KeySym(0x1008ff49);
|
||||
pub const SYM_XF86LaunchA: KeySym = KeySym(0x1008ff4a);
|
||||
pub const SYM_XF86LaunchB: KeySym = KeySym(0x1008ff4b);
|
||||
pub const SYM_XF86LaunchC: KeySym = KeySym(0x1008ff4c);
|
||||
pub const SYM_XF86LaunchD: KeySym = KeySym(0x1008ff4d);
|
||||
pub const SYM_XF86LaunchE: KeySym = KeySym(0x1008ff4e);
|
||||
pub const SYM_XF86LaunchF: KeySym = KeySym(0x1008ff4f);
|
||||
pub const SYM_XF86ApplicationLeft: KeySym = KeySym(0x1008ff50);
|
||||
pub const SYM_XF86ApplicationRight: KeySym = KeySym(0x1008ff51);
|
||||
pub const SYM_XF86Book: KeySym = KeySym(0x1008ff52);
|
||||
pub const SYM_XF86CD: KeySym = KeySym(0x1008ff53);
|
||||
pub const SYM_XF86Calculater: KeySym = KeySym(0x1008ff54);
|
||||
pub const SYM_XF86Clear: KeySym = KeySym(0x1008ff55);
|
||||
pub const SYM_XF86Close: KeySym = KeySym(0x1008ff56);
|
||||
pub const SYM_XF86Copy: KeySym = KeySym(0x1008ff57);
|
||||
pub const SYM_XF86Cut: KeySym = KeySym(0x1008ff58);
|
||||
pub const SYM_XF86Display: KeySym = KeySym(0x1008ff59);
|
||||
pub const SYM_XF86DOS: KeySym = KeySym(0x1008ff5a);
|
||||
pub const SYM_XF86Documents: KeySym = KeySym(0x1008ff5b);
|
||||
pub const SYM_XF86Excel: KeySym = KeySym(0x1008ff5c);
|
||||
pub const SYM_XF86Explorer: KeySym = KeySym(0x1008ff5d);
|
||||
pub const SYM_XF86Game: KeySym = KeySym(0x1008ff5e);
|
||||
pub const SYM_XF86Go: KeySym = KeySym(0x1008ff5f);
|
||||
pub const SYM_XF86iTouch: KeySym = KeySym(0x1008ff60);
|
||||
pub const SYM_XF86LogOff: KeySym = KeySym(0x1008ff61);
|
||||
pub const SYM_XF86Market: KeySym = KeySym(0x1008ff62);
|
||||
pub const SYM_XF86Meeting: KeySym = KeySym(0x1008ff63);
|
||||
pub const SYM_XF86MenuKB: KeySym = KeySym(0x1008ff65);
|
||||
pub const SYM_XF86MenuPB: KeySym = KeySym(0x1008ff66);
|
||||
pub const SYM_XF86MySites: KeySym = KeySym(0x1008ff67);
|
||||
pub const SYM_XF86New: KeySym = KeySym(0x1008ff68);
|
||||
pub const SYM_XF86News: KeySym = KeySym(0x1008ff69);
|
||||
pub const SYM_XF86OfficeHome: KeySym = KeySym(0x1008ff6a);
|
||||
pub const SYM_XF86Open: KeySym = KeySym(0x1008ff6b);
|
||||
pub const SYM_XF86Option: KeySym = KeySym(0x1008ff6c);
|
||||
pub const SYM_XF86Paste: KeySym = KeySym(0x1008ff6d);
|
||||
pub const SYM_XF86Phone: KeySym = KeySym(0x1008ff6e);
|
||||
pub const SYM_XF86Q: KeySym = KeySym(0x1008ff70);
|
||||
pub const SYM_XF86Reply: KeySym = KeySym(0x1008ff72);
|
||||
pub const SYM_XF86Reload: KeySym = KeySym(0x1008ff73);
|
||||
pub const SYM_XF86RotateWindows: KeySym = KeySym(0x1008ff74);
|
||||
pub const SYM_XF86RotationPB: KeySym = KeySym(0x1008ff75);
|
||||
pub const SYM_XF86RotationKB: KeySym = KeySym(0x1008ff76);
|
||||
pub const SYM_XF86Save: KeySym = KeySym(0x1008ff77);
|
||||
pub const SYM_XF86ScrollUp: KeySym = KeySym(0x1008ff78);
|
||||
pub const SYM_XF86ScrollDown: KeySym = KeySym(0x1008ff79);
|
||||
pub const SYM_XF86ScrollClick: KeySym = KeySym(0x1008ff7a);
|
||||
pub const SYM_XF86Send: KeySym = KeySym(0x1008ff7b);
|
||||
pub const SYM_XF86Spell: KeySym = KeySym(0x1008ff7c);
|
||||
pub const SYM_XF86SplitScreen: KeySym = KeySym(0x1008ff7d);
|
||||
pub const SYM_XF86Support: KeySym = KeySym(0x1008ff7e);
|
||||
pub const SYM_XF86TaskPane: KeySym = KeySym(0x1008ff7f);
|
||||
pub const SYM_XF86Terminal: KeySym = KeySym(0x1008ff80);
|
||||
pub const SYM_XF86Tools: KeySym = KeySym(0x1008ff81);
|
||||
pub const SYM_XF86Travel: KeySym = KeySym(0x1008ff82);
|
||||
pub const SYM_XF86UserPB: KeySym = KeySym(0x1008ff84);
|
||||
pub const SYM_XF86User1KB: KeySym = KeySym(0x1008ff85);
|
||||
pub const SYM_XF86User2KB: KeySym = KeySym(0x1008ff86);
|
||||
pub const SYM_XF86Video: KeySym = KeySym(0x1008ff87);
|
||||
pub const SYM_XF86WheelButton: KeySym = KeySym(0x1008ff88);
|
||||
pub const SYM_XF86Word: KeySym = KeySym(0x1008ff89);
|
||||
pub const SYM_XF86Xfer: KeySym = KeySym(0x1008ff8a);
|
||||
pub const SYM_XF86ZoomIn: KeySym = KeySym(0x1008ff8b);
|
||||
pub const SYM_XF86ZoomOut: KeySym = KeySym(0x1008ff8c);
|
||||
pub const SYM_XF86Away: KeySym = KeySym(0x1008ff8d);
|
||||
pub const SYM_XF86Messenger: KeySym = KeySym(0x1008ff8e);
|
||||
pub const SYM_XF86WebCam: KeySym = KeySym(0x1008ff8f);
|
||||
pub const SYM_XF86MailForward: KeySym = KeySym(0x1008ff90);
|
||||
pub const SYM_XF86Pictures: KeySym = KeySym(0x1008ff91);
|
||||
pub const SYM_XF86Music: KeySym = KeySym(0x1008ff92);
|
||||
pub const SYM_XF86Battery: KeySym = KeySym(0x1008ff93);
|
||||
pub const SYM_XF86Bluetooth: KeySym = KeySym(0x1008ff94);
|
||||
pub const SYM_XF86WLAN: KeySym = KeySym(0x1008ff95);
|
||||
pub const SYM_XF86UWB: KeySym = KeySym(0x1008ff96);
|
||||
pub const SYM_XF86AudioForward: KeySym = KeySym(0x1008ff97);
|
||||
pub const SYM_XF86AudioRepeat: KeySym = KeySym(0x1008ff98);
|
||||
pub const SYM_XF86AudioRandomPlay: KeySym = KeySym(0x1008ff99);
|
||||
pub const SYM_XF86Subtitle: KeySym = KeySym(0x1008ff9a);
|
||||
pub const SYM_XF86AudioCycleTrack: KeySym = KeySym(0x1008ff9b);
|
||||
pub const SYM_XF86CycleAngle: KeySym = KeySym(0x1008ff9c);
|
||||
pub const SYM_XF86FrameBack: KeySym = KeySym(0x1008ff9d);
|
||||
pub const SYM_XF86FrameForward: KeySym = KeySym(0x1008ff9e);
|
||||
pub const SYM_XF86Time: KeySym = KeySym(0x1008ff9f);
|
||||
pub const SYM_XF86Select: KeySym = KeySym(0x1008ffa0);
|
||||
pub const SYM_XF86View: KeySym = KeySym(0x1008ffa1);
|
||||
pub const SYM_XF86TopMenu: KeySym = KeySym(0x1008ffa2);
|
||||
pub const SYM_XF86Red: KeySym = KeySym(0x1008ffa3);
|
||||
pub const SYM_XF86Green: KeySym = KeySym(0x1008ffa4);
|
||||
pub const SYM_XF86Yellow: KeySym = KeySym(0x1008ffa5);
|
||||
pub const SYM_XF86Blue: KeySym = KeySym(0x1008ffa6);
|
||||
pub const SYM_XF86Suspend: KeySym = KeySym(0x1008ffa7);
|
||||
pub const SYM_XF86Hibernate: KeySym = KeySym(0x1008ffa8);
|
||||
pub const SYM_XF86TouchpadToggle: KeySym = KeySym(0x1008ffa9);
|
||||
pub const SYM_XF86TouchpadOn: KeySym = KeySym(0x1008ffb0);
|
||||
pub const SYM_XF86TouchpadOff: KeySym = KeySym(0x1008ffb1);
|
||||
pub const SYM_XF86AudioMicMute: KeySym = KeySym(0x1008ffb2);
|
||||
pub const SYM_XF86Keyboard: KeySym = KeySym(0x1008ffb3);
|
||||
pub const SYM_XF86WWAN: KeySym = KeySym(0x1008ffb4);
|
||||
pub const SYM_XF86RFKill: KeySym = KeySym(0x1008ffb5);
|
||||
pub const SYM_XF86AudioPreset: KeySym = KeySym(0x1008ffb6);
|
||||
pub const SYM_XF86RotationLockToggle: KeySym = KeySym(0x1008ffb7);
|
||||
pub const SYM_XF86FullScreen: KeySym = KeySym(0x1008ffb8);
|
||||
pub const SYM_XF86Switch_VT_1: KeySym = KeySym(0x1008fe01);
|
||||
pub const SYM_XF86Switch_VT_2: KeySym = KeySym(0x1008fe02);
|
||||
pub const SYM_XF86Switch_VT_3: KeySym = KeySym(0x1008fe03);
|
||||
pub const SYM_XF86Switch_VT_4: KeySym = KeySym(0x1008fe04);
|
||||
pub const SYM_XF86Switch_VT_5: KeySym = KeySym(0x1008fe05);
|
||||
pub const SYM_XF86Switch_VT_6: KeySym = KeySym(0x1008fe06);
|
||||
pub const SYM_XF86Switch_VT_7: KeySym = KeySym(0x1008fe07);
|
||||
pub const SYM_XF86Switch_VT_8: KeySym = KeySym(0x1008fe08);
|
||||
pub const SYM_XF86Switch_VT_9: KeySym = KeySym(0x1008fe09);
|
||||
pub const SYM_XF86Switch_VT_10: KeySym = KeySym(0x1008fe0a);
|
||||
pub const SYM_XF86Switch_VT_11: KeySym = KeySym(0x1008fe0b);
|
||||
pub const SYM_XF86Switch_VT_12: KeySym = KeySym(0x1008fe0c);
|
||||
pub const SYM_XF86Ungrab: KeySym = KeySym(0x1008fe20);
|
||||
pub const SYM_XF86ClearGrab: KeySym = KeySym(0x1008fe21);
|
||||
pub const SYM_XF86Next_VMode: KeySym = KeySym(0x1008fe22);
|
||||
pub const SYM_XF86Prev_VMode: KeySym = KeySym(0x1008fe23);
|
||||
pub const SYM_XF86LogWindowTree: KeySym = KeySym(0x1008fe24);
|
||||
pub const SYM_XF86LogGrabInfo: KeySym = KeySym(0x1008fe25);
|
||||
pub const SYM_XF86BrightnessAuto: KeySym = KeySym(0x100810f4);
|
||||
pub const SYM_XF86DisplayOff: KeySym = KeySym(0x100810f5);
|
||||
pub const SYM_XF86Info: KeySym = KeySym(0x10081166);
|
||||
|
|
@ -2362,6 +2370,11 @@ pub const SYM_XF86AppSelect: KeySym = KeySym(0x10081244);
|
|||
pub const SYM_XF86Screensaver: KeySym = KeySym(0x10081245);
|
||||
pub const SYM_XF86VoiceCommand: KeySym = KeySym(0x10081246);
|
||||
pub const SYM_XF86Assistant: KeySym = KeySym(0x10081247);
|
||||
pub const SYM_XF86EmojiPicker: KeySym = KeySym(0x10081249);
|
||||
pub const SYM_XF86Dictate: KeySym = KeySym(0x1008124a);
|
||||
pub const SYM_XF86CameraAccessEnable: KeySym = KeySym(0x1008124b);
|
||||
pub const SYM_XF86CameraAccessDisable: KeySym = KeySym(0x1008124c);
|
||||
pub const SYM_XF86CameraAccessToggle: KeySym = KeySym(0x1008124d);
|
||||
pub const SYM_XF86BrightnessMin: KeySym = KeySym(0x10081250);
|
||||
pub const SYM_XF86BrightnessMax: KeySym = KeySym(0x10081251);
|
||||
pub const SYM_XF86KbdInputAssistPrev: KeySym = KeySym(0x10081260);
|
||||
|
|
@ -2391,6 +2404,20 @@ pub const SYM_XF86Data: KeySym = KeySym(0x10081277);
|
|||
pub const SYM_XF86OnScreenKeyboard: KeySym = KeySym(0x10081278);
|
||||
pub const SYM_XF86PrivacyScreenToggle: KeySym = KeySym(0x10081279);
|
||||
pub const SYM_XF86SelectiveScreenshot: KeySym = KeySym(0x1008127a);
|
||||
pub const SYM_XF86NextElement: KeySym = KeySym(0x1008127b);
|
||||
pub const SYM_XF86PreviousElement: KeySym = KeySym(0x1008127c);
|
||||
pub const SYM_XF86AutopilotEngageToggle: KeySym = KeySym(0x1008127d);
|
||||
pub const SYM_XF86MarkWaypoint: KeySym = KeySym(0x1008127e);
|
||||
pub const SYM_XF86Sos: KeySym = KeySym(0x1008127f);
|
||||
pub const SYM_XF86NavChart: KeySym = KeySym(0x10081280);
|
||||
pub const SYM_XF86FishingChart: KeySym = KeySym(0x10081281);
|
||||
pub const SYM_XF86SingleRangeRadar: KeySym = KeySym(0x10081282);
|
||||
pub const SYM_XF86DualRangeRadar: KeySym = KeySym(0x10081283);
|
||||
pub const SYM_XF86RadarOverlay: KeySym = KeySym(0x10081284);
|
||||
pub const SYM_XF86TraditionalSonar: KeySym = KeySym(0x10081285);
|
||||
pub const SYM_XF86ClearvuSonar: KeySym = KeySym(0x10081286);
|
||||
pub const SYM_XF86SidevuSonar: KeySym = KeySym(0x10081287);
|
||||
pub const SYM_XF86NavInfo: KeySym = KeySym(0x10081288);
|
||||
pub const SYM_XF86Macro1: KeySym = KeySym(0x10081290);
|
||||
pub const SYM_XF86Macro2: KeySym = KeySym(0x10081291);
|
||||
pub const SYM_XF86Macro3: KeySym = KeySym(0x10081292);
|
||||
|
|
@ -2432,121 +2459,121 @@ pub const SYM_XF86KbdLcdMenu2: KeySym = KeySym(0x100812b9);
|
|||
pub const SYM_XF86KbdLcdMenu3: KeySym = KeySym(0x100812ba);
|
||||
pub const SYM_XF86KbdLcdMenu4: KeySym = KeySym(0x100812bb);
|
||||
pub const SYM_XF86KbdLcdMenu5: KeySym = KeySym(0x100812bc);
|
||||
pub const SYM_SunFA_Grave: KeySym = KeySym(0x1005FF00);
|
||||
pub const SYM_SunFA_Circum: KeySym = KeySym(0x1005FF01);
|
||||
pub const SYM_SunFA_Tilde: KeySym = KeySym(0x1005FF02);
|
||||
pub const SYM_SunFA_Acute: KeySym = KeySym(0x1005FF03);
|
||||
pub const SYM_SunFA_Diaeresis: KeySym = KeySym(0x1005FF04);
|
||||
pub const SYM_SunFA_Cedilla: KeySym = KeySym(0x1005FF05);
|
||||
pub const SYM_SunF36: KeySym = KeySym(0x1005FF10);
|
||||
pub const SYM_SunF37: KeySym = KeySym(0x1005FF11);
|
||||
pub const SYM_SunSys_Req: KeySym = KeySym(0x1005FF60);
|
||||
pub const SYM_SunPrint_Screen: KeySym = KeySym(0x0000FF61);
|
||||
pub const SYM_SunCompose: KeySym = KeySym(0x0000FF20);
|
||||
pub const SYM_SunAltGraph: KeySym = KeySym(0x0000FF7E);
|
||||
pub const SYM_SunPageUp: KeySym = KeySym(0x0000FF55);
|
||||
pub const SYM_SunPageDown: KeySym = KeySym(0x0000FF56);
|
||||
pub const SYM_SunUndo: KeySym = KeySym(0x0000FF65);
|
||||
pub const SYM_SunAgain: KeySym = KeySym(0x0000FF66);
|
||||
pub const SYM_SunFind: KeySym = KeySym(0x0000FF68);
|
||||
pub const SYM_SunStop: KeySym = KeySym(0x0000FF69);
|
||||
pub const SYM_SunProps: KeySym = KeySym(0x1005FF70);
|
||||
pub const SYM_SunFront: KeySym = KeySym(0x1005FF71);
|
||||
pub const SYM_SunCopy: KeySym = KeySym(0x1005FF72);
|
||||
pub const SYM_SunOpen: KeySym = KeySym(0x1005FF73);
|
||||
pub const SYM_SunPaste: KeySym = KeySym(0x1005FF74);
|
||||
pub const SYM_SunCut: KeySym = KeySym(0x1005FF75);
|
||||
pub const SYM_SunPowerSwitch: KeySym = KeySym(0x1005FF76);
|
||||
pub const SYM_SunAudioLowerVolume: KeySym = KeySym(0x1005FF77);
|
||||
pub const SYM_SunAudioMute: KeySym = KeySym(0x1005FF78);
|
||||
pub const SYM_SunAudioRaiseVolume: KeySym = KeySym(0x1005FF79);
|
||||
pub const SYM_SunVideoDegauss: KeySym = KeySym(0x1005FF7A);
|
||||
pub const SYM_SunVideoLowerBrightness: KeySym = KeySym(0x1005FF7B);
|
||||
pub const SYM_SunVideoRaiseBrightness: KeySym = KeySym(0x1005FF7C);
|
||||
pub const SYM_SunPowerSwitchShift: KeySym = KeySym(0x1005FF7D);
|
||||
pub const SYM_Dring_accent: KeySym = KeySym(0x1000FEB0);
|
||||
pub const SYM_Dcircumflex_accent: KeySym = KeySym(0x1000FE5E);
|
||||
pub const SYM_Dcedilla_accent: KeySym = KeySym(0x1000FE2C);
|
||||
pub const SYM_Dacute_accent: KeySym = KeySym(0x1000FE27);
|
||||
pub const SYM_Dgrave_accent: KeySym = KeySym(0x1000FE60);
|
||||
pub const SYM_Dtilde: KeySym = KeySym(0x1000FE7E);
|
||||
pub const SYM_Ddiaeresis: KeySym = KeySym(0x1000FE22);
|
||||
pub const SYM_DRemove: KeySym = KeySym(0x1000FF00);
|
||||
pub const SYM_hpClearLine: KeySym = KeySym(0x1000FF6F);
|
||||
pub const SYM_hpInsertLine: KeySym = KeySym(0x1000FF70);
|
||||
pub const SYM_hpDeleteLine: KeySym = KeySym(0x1000FF71);
|
||||
pub const SYM_hpInsertChar: KeySym = KeySym(0x1000FF72);
|
||||
pub const SYM_hpDeleteChar: KeySym = KeySym(0x1000FF73);
|
||||
pub const SYM_hpBackTab: KeySym = KeySym(0x1000FF74);
|
||||
pub const SYM_hpKP_BackTab: KeySym = KeySym(0x1000FF75);
|
||||
pub const SYM_hpModelock1: KeySym = KeySym(0x1000FF48);
|
||||
pub const SYM_hpModelock2: KeySym = KeySym(0x1000FF49);
|
||||
pub const SYM_hpReset: KeySym = KeySym(0x1000FF6C);
|
||||
pub const SYM_hpSystem: KeySym = KeySym(0x1000FF6D);
|
||||
pub const SYM_hpUser: KeySym = KeySym(0x1000FF6E);
|
||||
pub const SYM_hpmute_acute: KeySym = KeySym(0x100000A8);
|
||||
pub const SYM_hpmute_grave: KeySym = KeySym(0x100000A9);
|
||||
pub const SYM_hpmute_asciicircum: KeySym = KeySym(0x100000AA);
|
||||
pub const SYM_hpmute_diaeresis: KeySym = KeySym(0x100000AB);
|
||||
pub const SYM_hpmute_asciitilde: KeySym = KeySym(0x100000AC);
|
||||
pub const SYM_hplira: KeySym = KeySym(0x100000AF);
|
||||
pub const SYM_hpguilder: KeySym = KeySym(0x100000BE);
|
||||
pub const SYM_hpYdiaeresis: KeySym = KeySym(0x100000EE);
|
||||
pub const SYM_hpIO: KeySym = KeySym(0x100000EE);
|
||||
pub const SYM_hplongminus: KeySym = KeySym(0x100000F6);
|
||||
pub const SYM_hpblock: KeySym = KeySym(0x100000FC);
|
||||
pub const SYM_osfCopy: KeySym = KeySym(0x1004FF02);
|
||||
pub const SYM_osfCut: KeySym = KeySym(0x1004FF03);
|
||||
pub const SYM_osfPaste: KeySym = KeySym(0x1004FF04);
|
||||
pub const SYM_osfBackTab: KeySym = KeySym(0x1004FF07);
|
||||
pub const SYM_osfBackSpace: KeySym = KeySym(0x1004FF08);
|
||||
pub const SYM_osfClear: KeySym = KeySym(0x1004FF0B);
|
||||
pub const SYM_osfEscape: KeySym = KeySym(0x1004FF1B);
|
||||
pub const SYM_osfAddMode: KeySym = KeySym(0x1004FF31);
|
||||
pub const SYM_osfPrimaryPaste: KeySym = KeySym(0x1004FF32);
|
||||
pub const SYM_osfQuickPaste: KeySym = KeySym(0x1004FF33);
|
||||
pub const SYM_osfPageLeft: KeySym = KeySym(0x1004FF40);
|
||||
pub const SYM_osfPageUp: KeySym = KeySym(0x1004FF41);
|
||||
pub const SYM_osfPageDown: KeySym = KeySym(0x1004FF42);
|
||||
pub const SYM_osfPageRight: KeySym = KeySym(0x1004FF43);
|
||||
pub const SYM_osfActivate: KeySym = KeySym(0x1004FF44);
|
||||
pub const SYM_osfMenuBar: KeySym = KeySym(0x1004FF45);
|
||||
pub const SYM_osfLeft: KeySym = KeySym(0x1004FF51);
|
||||
pub const SYM_osfUp: KeySym = KeySym(0x1004FF52);
|
||||
pub const SYM_osfRight: KeySym = KeySym(0x1004FF53);
|
||||
pub const SYM_osfDown: KeySym = KeySym(0x1004FF54);
|
||||
pub const SYM_osfEndLine: KeySym = KeySym(0x1004FF57);
|
||||
pub const SYM_osfBeginLine: KeySym = KeySym(0x1004FF58);
|
||||
pub const SYM_osfEndData: KeySym = KeySym(0x1004FF59);
|
||||
pub const SYM_osfBeginData: KeySym = KeySym(0x1004FF5A);
|
||||
pub const SYM_osfPrevMenu: KeySym = KeySym(0x1004FF5B);
|
||||
pub const SYM_osfNextMenu: KeySym = KeySym(0x1004FF5C);
|
||||
pub const SYM_osfPrevField: KeySym = KeySym(0x1004FF5D);
|
||||
pub const SYM_osfNextField: KeySym = KeySym(0x1004FF5E);
|
||||
pub const SYM_osfSelect: KeySym = KeySym(0x1004FF60);
|
||||
pub const SYM_osfInsert: KeySym = KeySym(0x1004FF63);
|
||||
pub const SYM_osfUndo: KeySym = KeySym(0x1004FF65);
|
||||
pub const SYM_osfMenu: KeySym = KeySym(0x1004FF67);
|
||||
pub const SYM_osfCancel: KeySym = KeySym(0x1004FF69);
|
||||
pub const SYM_osfHelp: KeySym = KeySym(0x1004FF6A);
|
||||
pub const SYM_osfSelectAll: KeySym = KeySym(0x1004FF71);
|
||||
pub const SYM_osfDeselectAll: KeySym = KeySym(0x1004FF72);
|
||||
pub const SYM_osfReselect: KeySym = KeySym(0x1004FF73);
|
||||
pub const SYM_osfExtend: KeySym = KeySym(0x1004FF74);
|
||||
pub const SYM_osfRestore: KeySym = KeySym(0x1004FF78);
|
||||
pub const SYM_osfDelete: KeySym = KeySym(0x1004FFFF);
|
||||
pub const SYM_Reset: KeySym = KeySym(0x1000FF6C);
|
||||
pub const SYM_System: KeySym = KeySym(0x1000FF6D);
|
||||
pub const SYM_User: KeySym = KeySym(0x1000FF6E);
|
||||
pub const SYM_ClearLine: KeySym = KeySym(0x1000FF6F);
|
||||
pub const SYM_InsertLine: KeySym = KeySym(0x1000FF70);
|
||||
pub const SYM_DeleteLine: KeySym = KeySym(0x1000FF71);
|
||||
pub const SYM_InsertChar: KeySym = KeySym(0x1000FF72);
|
||||
pub const SYM_DeleteChar: KeySym = KeySym(0x1000FF73);
|
||||
pub const SYM_BackTab: KeySym = KeySym(0x1000FF74);
|
||||
pub const SYM_KP_BackTab: KeySym = KeySym(0x1000FF75);
|
||||
pub const SYM_Ext16bit_L: KeySym = KeySym(0x1000FF76);
|
||||
pub const SYM_Ext16bit_R: KeySym = KeySym(0x1000FF77);
|
||||
pub const SYM_SunFA_Grave: KeySym = KeySym(0x1005ff00);
|
||||
pub const SYM_SunFA_Circum: KeySym = KeySym(0x1005ff01);
|
||||
pub const SYM_SunFA_Tilde: KeySym = KeySym(0x1005ff02);
|
||||
pub const SYM_SunFA_Acute: KeySym = KeySym(0x1005ff03);
|
||||
pub const SYM_SunFA_Diaeresis: KeySym = KeySym(0x1005ff04);
|
||||
pub const SYM_SunFA_Cedilla: KeySym = KeySym(0x1005ff05);
|
||||
pub const SYM_SunF36: KeySym = KeySym(0x1005ff10);
|
||||
pub const SYM_SunF37: KeySym = KeySym(0x1005ff11);
|
||||
pub const SYM_SunSys_Req: KeySym = KeySym(0x1005ff60);
|
||||
pub const SYM_SunPrint_Screen: KeySym = KeySym(0x0000ff61);
|
||||
pub const SYM_SunCompose: KeySym = KeySym(0x0000ff20);
|
||||
pub const SYM_SunAltGraph: KeySym = KeySym(0x0000ff7e);
|
||||
pub const SYM_SunPageUp: KeySym = KeySym(0x0000ff55);
|
||||
pub const SYM_SunPageDown: KeySym = KeySym(0x0000ff56);
|
||||
pub const SYM_SunUndo: KeySym = KeySym(0x0000ff65);
|
||||
pub const SYM_SunAgain: KeySym = KeySym(0x0000ff66);
|
||||
pub const SYM_SunFind: KeySym = KeySym(0x0000ff68);
|
||||
pub const SYM_SunStop: KeySym = KeySym(0x0000ff69);
|
||||
pub const SYM_SunProps: KeySym = KeySym(0x1005ff70);
|
||||
pub const SYM_SunFront: KeySym = KeySym(0x1005ff71);
|
||||
pub const SYM_SunCopy: KeySym = KeySym(0x1005ff72);
|
||||
pub const SYM_SunOpen: KeySym = KeySym(0x1005ff73);
|
||||
pub const SYM_SunPaste: KeySym = KeySym(0x1005ff74);
|
||||
pub const SYM_SunCut: KeySym = KeySym(0x1005ff75);
|
||||
pub const SYM_SunPowerSwitch: KeySym = KeySym(0x1005ff76);
|
||||
pub const SYM_SunAudioLowerVolume: KeySym = KeySym(0x1005ff77);
|
||||
pub const SYM_SunAudioMute: KeySym = KeySym(0x1005ff78);
|
||||
pub const SYM_SunAudioRaiseVolume: KeySym = KeySym(0x1005ff79);
|
||||
pub const SYM_SunVideoDegauss: KeySym = KeySym(0x1005ff7a);
|
||||
pub const SYM_SunVideoLowerBrightness: KeySym = KeySym(0x1005ff7b);
|
||||
pub const SYM_SunVideoRaiseBrightness: KeySym = KeySym(0x1005ff7c);
|
||||
pub const SYM_SunPowerSwitchShift: KeySym = KeySym(0x1005ff7d);
|
||||
pub const SYM_Dring_accent: KeySym = KeySym(0x1000feb0);
|
||||
pub const SYM_Dcircumflex_accent: KeySym = KeySym(0x1000fe5e);
|
||||
pub const SYM_Dcedilla_accent: KeySym = KeySym(0x1000fe2c);
|
||||
pub const SYM_Dacute_accent: KeySym = KeySym(0x1000fe27);
|
||||
pub const SYM_Dgrave_accent: KeySym = KeySym(0x1000fe60);
|
||||
pub const SYM_Dtilde: KeySym = KeySym(0x1000fe7e);
|
||||
pub const SYM_Ddiaeresis: KeySym = KeySym(0x1000fe22);
|
||||
pub const SYM_DRemove: KeySym = KeySym(0x1000ff00);
|
||||
pub const SYM_hpClearLine: KeySym = KeySym(0x1000ff6f);
|
||||
pub const SYM_hpInsertLine: KeySym = KeySym(0x1000ff70);
|
||||
pub const SYM_hpDeleteLine: KeySym = KeySym(0x1000ff71);
|
||||
pub const SYM_hpInsertChar: KeySym = KeySym(0x1000ff72);
|
||||
pub const SYM_hpDeleteChar: KeySym = KeySym(0x1000ff73);
|
||||
pub const SYM_hpBackTab: KeySym = KeySym(0x1000ff74);
|
||||
pub const SYM_hpKP_BackTab: KeySym = KeySym(0x1000ff75);
|
||||
pub const SYM_hpModelock1: KeySym = KeySym(0x1000ff48);
|
||||
pub const SYM_hpModelock2: KeySym = KeySym(0x1000ff49);
|
||||
pub const SYM_hpReset: KeySym = KeySym(0x1000ff6c);
|
||||
pub const SYM_hpSystem: KeySym = KeySym(0x1000ff6d);
|
||||
pub const SYM_hpUser: KeySym = KeySym(0x1000ff6e);
|
||||
pub const SYM_hpmute_acute: KeySym = KeySym(0x100000a8);
|
||||
pub const SYM_hpmute_grave: KeySym = KeySym(0x100000a9);
|
||||
pub const SYM_hpmute_asciicircum: KeySym = KeySym(0x100000aa);
|
||||
pub const SYM_hpmute_diaeresis: KeySym = KeySym(0x100000ab);
|
||||
pub const SYM_hpmute_asciitilde: KeySym = KeySym(0x100000ac);
|
||||
pub const SYM_hplira: KeySym = KeySym(0x100000af);
|
||||
pub const SYM_hpguilder: KeySym = KeySym(0x100000be);
|
||||
pub const SYM_hpYdiaeresis: KeySym = KeySym(0x100000ee);
|
||||
pub const SYM_hpIO: KeySym = KeySym(0x100000ee);
|
||||
pub const SYM_hplongminus: KeySym = KeySym(0x100000f6);
|
||||
pub const SYM_hpblock: KeySym = KeySym(0x100000fc);
|
||||
pub const SYM_osfCopy: KeySym = KeySym(0x1004ff02);
|
||||
pub const SYM_osfCut: KeySym = KeySym(0x1004ff03);
|
||||
pub const SYM_osfPaste: KeySym = KeySym(0x1004ff04);
|
||||
pub const SYM_osfBackTab: KeySym = KeySym(0x1004ff07);
|
||||
pub const SYM_osfBackSpace: KeySym = KeySym(0x1004ff08);
|
||||
pub const SYM_osfClear: KeySym = KeySym(0x1004ff0b);
|
||||
pub const SYM_osfEscape: KeySym = KeySym(0x1004ff1b);
|
||||
pub const SYM_osfAddMode: KeySym = KeySym(0x1004ff31);
|
||||
pub const SYM_osfPrimaryPaste: KeySym = KeySym(0x1004ff32);
|
||||
pub const SYM_osfQuickPaste: KeySym = KeySym(0x1004ff33);
|
||||
pub const SYM_osfPageLeft: KeySym = KeySym(0x1004ff40);
|
||||
pub const SYM_osfPageUp: KeySym = KeySym(0x1004ff41);
|
||||
pub const SYM_osfPageDown: KeySym = KeySym(0x1004ff42);
|
||||
pub const SYM_osfPageRight: KeySym = KeySym(0x1004ff43);
|
||||
pub const SYM_osfActivate: KeySym = KeySym(0x1004ff44);
|
||||
pub const SYM_osfMenuBar: KeySym = KeySym(0x1004ff45);
|
||||
pub const SYM_osfLeft: KeySym = KeySym(0x1004ff51);
|
||||
pub const SYM_osfUp: KeySym = KeySym(0x1004ff52);
|
||||
pub const SYM_osfRight: KeySym = KeySym(0x1004ff53);
|
||||
pub const SYM_osfDown: KeySym = KeySym(0x1004ff54);
|
||||
pub const SYM_osfEndLine: KeySym = KeySym(0x1004ff57);
|
||||
pub const SYM_osfBeginLine: KeySym = KeySym(0x1004ff58);
|
||||
pub const SYM_osfEndData: KeySym = KeySym(0x1004ff59);
|
||||
pub const SYM_osfBeginData: KeySym = KeySym(0x1004ff5a);
|
||||
pub const SYM_osfPrevMenu: KeySym = KeySym(0x1004ff5b);
|
||||
pub const SYM_osfNextMenu: KeySym = KeySym(0x1004ff5c);
|
||||
pub const SYM_osfPrevField: KeySym = KeySym(0x1004ff5d);
|
||||
pub const SYM_osfNextField: KeySym = KeySym(0x1004ff5e);
|
||||
pub const SYM_osfSelect: KeySym = KeySym(0x1004ff60);
|
||||
pub const SYM_osfInsert: KeySym = KeySym(0x1004ff63);
|
||||
pub const SYM_osfUndo: KeySym = KeySym(0x1004ff65);
|
||||
pub const SYM_osfMenu: KeySym = KeySym(0x1004ff67);
|
||||
pub const SYM_osfCancel: KeySym = KeySym(0x1004ff69);
|
||||
pub const SYM_osfHelp: KeySym = KeySym(0x1004ff6a);
|
||||
pub const SYM_osfSelectAll: KeySym = KeySym(0x1004ff71);
|
||||
pub const SYM_osfDeselectAll: KeySym = KeySym(0x1004ff72);
|
||||
pub const SYM_osfReselect: KeySym = KeySym(0x1004ff73);
|
||||
pub const SYM_osfExtend: KeySym = KeySym(0x1004ff74);
|
||||
pub const SYM_osfRestore: KeySym = KeySym(0x1004ff78);
|
||||
pub const SYM_osfDelete: KeySym = KeySym(0x1004ffff);
|
||||
pub const SYM_Reset: KeySym = KeySym(0x1000ff6c);
|
||||
pub const SYM_System: KeySym = KeySym(0x1000ff6d);
|
||||
pub const SYM_User: KeySym = KeySym(0x1000ff6e);
|
||||
pub const SYM_ClearLine: KeySym = KeySym(0x1000ff6f);
|
||||
pub const SYM_InsertLine: KeySym = KeySym(0x1000ff70);
|
||||
pub const SYM_DeleteLine: KeySym = KeySym(0x1000ff71);
|
||||
pub const SYM_InsertChar: KeySym = KeySym(0x1000ff72);
|
||||
pub const SYM_DeleteChar: KeySym = KeySym(0x1000ff73);
|
||||
pub const SYM_BackTab: KeySym = KeySym(0x1000ff74);
|
||||
pub const SYM_KP_BackTab: KeySym = KeySym(0x1000ff75);
|
||||
pub const SYM_Ext16bit_L: KeySym = KeySym(0x1000ff76);
|
||||
pub const SYM_Ext16bit_R: KeySym = KeySym(0x1000ff77);
|
||||
pub const SYM_mute_acute: KeySym = KeySym(0x100000a8);
|
||||
pub const SYM_mute_grave: KeySym = KeySym(0x100000a9);
|
||||
pub const SYM_mute_asciicircum: KeySym = KeySym(0x100000aa);
|
||||
|
|
|
|||
|
|
@ -12,23 +12,19 @@
|
|||
//! config!(configure);
|
||||
//! ```
|
||||
//!
|
||||
//! This configuration will not allow you to interact with the compositor at all nor exit it.
|
||||
//! This configuration will not allow you to exit the compositor.
|
||||
//! To add at least that much functionality, add the following code to `configure`:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use jay_config::{config, quit};
|
||||
//! use jay_config::input::{get_seat, input_devices, on_new_input_device};
|
||||
//! use jay_config::input::{get_default_seat, input_devices, on_new_input_device};
|
||||
//! use jay_config::keyboard::mods::ALT;
|
||||
//! use jay_config::keyboard::syms::SYM_q;
|
||||
//!
|
||||
//! fn configure() {
|
||||
//! // Create a seat.
|
||||
//! let seat = get_seat("default");
|
||||
//! let seat = get_default_seat();
|
||||
//! // Create a key binding to exit the compositor.
|
||||
//! seat.bind(ALT | SYM_q, || quit());
|
||||
//! // Assign all current and future input devices to this seat.
|
||||
//! input_devices().into_iter().for_each(move |d| d.set_seat(seat));
|
||||
//! on_new_input_device(move |d| d.set_seat(seat));
|
||||
//! }
|
||||
//!
|
||||
//! config!(configure);
|
||||
|
|
@ -46,7 +42,10 @@
|
|||
use {
|
||||
crate::keyboard::ModifiedKeySym,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::fmt::{Debug, Display, Formatter},
|
||||
std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
time::Duration,
|
||||
},
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
|
|
@ -199,3 +198,20 @@ pub fn on_idle<F: FnMut() + 'static>(f: F) {
|
|||
pub fn on_devices_enumerated<F: FnOnce() + 'static>(f: F) {
|
||||
get!().on_devices_enumerated(f)
|
||||
}
|
||||
|
||||
/// Returns the Jay config directory.
|
||||
pub fn config_dir() -> String {
|
||||
get!().config_dir()
|
||||
}
|
||||
|
||||
/// Returns all visible workspaces.
|
||||
pub fn workspaces() -> Vec<Workspace> {
|
||||
get!().workspaces()
|
||||
}
|
||||
|
||||
/// Configures the idle timeout.
|
||||
///
|
||||
/// `None` disables the timeout.
|
||||
pub fn set_idle(timeout: Option<Duration>) {
|
||||
get!().set_idle(timeout.unwrap_or_default())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,3 +14,8 @@ pub enum LogLevel {
|
|||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
/// Sets the log level of the compositor.
|
||||
pub fn set_log_level(level: LogLevel) {
|
||||
get!().set_log_level(level);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
|
|||
///
|
||||
/// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight.
|
||||
// values are stored premultiplied
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
pub struct Color {
|
||||
r: f32,
|
||||
g: f32,
|
||||
|
|
@ -32,13 +32,13 @@ fn to_u8(c: f32) -> u8 {
|
|||
}
|
||||
|
||||
fn validate_f32(f: f32) -> bool {
|
||||
f.is_normal() && f >= 0.0 && f <= 1.0
|
||||
f >= 0.0 && f <= 1.0
|
||||
}
|
||||
|
||||
fn validate_f32_all(f: [f32; 4]) -> bool {
|
||||
if !f.into_iter().all(validate_f32) {
|
||||
log::warn!(
|
||||
"f32 values {:?} are not in the valid color range. Using solid black instead",
|
||||
"f32 values {:?} are not in the valid color range. Using solid black instead xyz",
|
||||
f
|
||||
);
|
||||
return false;
|
||||
|
|
@ -72,7 +72,7 @@ impl Color {
|
|||
|
||||
/// Creates a new color from premultiplied `f32` RGBA values.
|
||||
pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
if validate_f32_all([r, g, b, a]) {
|
||||
if !validate_f32_all([r, g, b, a]) {
|
||||
Self::BLACK
|
||||
} else if r > a || g > a || b > a {
|
||||
log::warn!("f32 values {:?} are not valid valid for a premultiplied color. Using solid black instead.", [r, g, b, a]);
|
||||
|
|
@ -84,7 +84,7 @@ impl Color {
|
|||
|
||||
/// Creates a new color from straight `f32` RGBA values.
|
||||
pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
if validate_f32_all([r, g, b, a]) {
|
||||
if !validate_f32_all([r, g, b, a]) {
|
||||
Self::BLACK
|
||||
} else {
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -178,6 +178,14 @@ impl Connector {
|
|||
self.mode().refresh_millihz
|
||||
}
|
||||
|
||||
/// Retrieves the position of the output in the global compositor space.
|
||||
pub fn position(self) -> (i32, i32) {
|
||||
if !self.connected() {
|
||||
return (0, 0);
|
||||
}
|
||||
get!().connector_get_position(self)
|
||||
}
|
||||
|
||||
/// Sets the position of the connector in the global compositor space.
|
||||
///
|
||||
/// `x` and `y` must be non-negative and must not exceed a currently unspecified limit.
|
||||
|
|
@ -212,6 +220,34 @@ impl Connector {
|
|||
}
|
||||
get!().connector_set_transform(self, transform);
|
||||
}
|
||||
|
||||
pub fn name(self) -> String {
|
||||
if !self.exists() {
|
||||
return String::new();
|
||||
}
|
||||
get!(String::new()).connector_get_name(self)
|
||||
}
|
||||
|
||||
pub fn model(self) -> String {
|
||||
if !self.exists() {
|
||||
return String::new();
|
||||
}
|
||||
get!(String::new()).connector_get_model(self)
|
||||
}
|
||||
|
||||
pub fn manufacturer(self) -> String {
|
||||
if !self.exists() {
|
||||
return String::new();
|
||||
}
|
||||
get!(String::new()).connector_get_manufacturer(self)
|
||||
}
|
||||
|
||||
pub fn serial_number(self) -> String {
|
||||
if !self.exists() {
|
||||
return String::new();
|
||||
}
|
||||
get!(String::new()).connector_get_serial_number(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all available DRM devices.
|
||||
|
|
@ -247,6 +283,10 @@ pub fn on_graphics_initialized<F: FnOnce() + 'static>(f: F) {
|
|||
get!().on_graphics_initialized(f)
|
||||
}
|
||||
|
||||
pub fn connectors() -> Vec<Connector> {
|
||||
get!().connectors(None)
|
||||
}
|
||||
|
||||
/// Returns the connector with the given id.
|
||||
///
|
||||
/// The linux kernel identifies connectors by a (type, idx) tuple, e.g., `DP-0`.
|
||||
|
|
@ -381,7 +421,14 @@ pub struct DrmDevice(pub u64);
|
|||
impl DrmDevice {
|
||||
/// Returns the connectors of this device.
|
||||
pub fn connectors(self) -> Vec<Connector> {
|
||||
get!().device_connectors(self)
|
||||
get!().connectors(Some(self))
|
||||
}
|
||||
|
||||
/// Returns the devnode of this device.
|
||||
///
|
||||
/// E.g. `/dev/dri/card0`.
|
||||
pub fn devnode(self) -> String {
|
||||
get!().drm_device_devnode(self)
|
||||
}
|
||||
|
||||
/// Returns the syspath of this device.
|
||||
|
|
|
|||
|
|
@ -602,10 +602,10 @@ impl MetalConnector {
|
|||
&self.state,
|
||||
Some(output.global.pos.get()),
|
||||
Some(rr),
|
||||
output.global.preferred_scale.get(),
|
||||
output.global.persistent.scale.get(),
|
||||
render_hw_cursor,
|
||||
output.has_fullscreen(),
|
||||
output.global.transform.get(),
|
||||
output.global.persistent.transform.get(),
|
||||
);
|
||||
let try_direct_scanout = try_direct_scanout
|
||||
&& self.direct_scanout_enabled()
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ impl Randr {
|
|||
tc.send(jay_randr::SetScale {
|
||||
self_id: randr,
|
||||
output: &args.output,
|
||||
scale: scale.0,
|
||||
scale: scale.to_wl(),
|
||||
});
|
||||
}
|
||||
OutputCommand::Mode(t) => {
|
||||
|
|
@ -557,7 +557,7 @@ impl Randr {
|
|||
let mut data = data.borrow_mut();
|
||||
let c = data.connectors.last_mut().unwrap();
|
||||
c.output = Some(Output {
|
||||
scale: Scale(msg.scale).to_f64(),
|
||||
scale: Scale::from_wl(msg.scale).to_f64(),
|
||||
width: msg.width,
|
||||
height: msg.height,
|
||||
x: msg.x,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ use {
|
|||
dbus::Dbus,
|
||||
forker,
|
||||
globals::Globals,
|
||||
ifs::{wl_output::WlOutputGlobal, wl_surface::NoneSurfaceExt},
|
||||
ifs::{
|
||||
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||
wl_surface::NoneSurfaceExt,
|
||||
},
|
||||
io_uring::{IoUring, IoUringError},
|
||||
leaks,
|
||||
logger::Logger,
|
||||
|
|
@ -39,7 +42,7 @@ use {
|
|||
},
|
||||
ahash::AHashSet,
|
||||
forker::ForkerProxy,
|
||||
jay_config::video::GfxApi,
|
||||
jay_config::{_private::DEFAULT_SEAT_NAME, video::GfxApi},
|
||||
std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration},
|
||||
thiserror::Error,
|
||||
uapi::c,
|
||||
|
|
@ -204,9 +207,10 @@ fn start_compositor2(
|
|||
dma_buf_ids: Default::default(),
|
||||
drm_feedback_ids: Default::default(),
|
||||
direct_scanout_enabled: Cell::new(true),
|
||||
output_transforms: Default::default(),
|
||||
persistent_output_states: Default::default(),
|
||||
double_click_interval_usec: Cell::new(400 * 1000),
|
||||
double_click_distance: Cell::new(5),
|
||||
create_default_seat: Cell::new(true),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
@ -254,6 +258,10 @@ async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
|
|||
config.configure(false);
|
||||
state.config.set(Some(Rc::new(config)));
|
||||
|
||||
if state.create_default_seat.get() && state.globals.seats.is_empty() {
|
||||
state.create_seat(DEFAULT_SEAT_NAME);
|
||||
}
|
||||
|
||||
let _geh = start_global_event_handlers(&state, &backend);
|
||||
state.start_xwayland();
|
||||
|
||||
|
|
@ -359,6 +367,17 @@ fn init_fd_limit() {
|
|||
}
|
||||
|
||||
fn create_dummy_output(state: &Rc<State>) {
|
||||
let output_id = Rc::new(OutputId {
|
||||
connector: "jay-dummy-connector".to_string(),
|
||||
manufacturer: "jay".to_string(),
|
||||
model: "jay-dummy-output".to_string(),
|
||||
serial_number: "".to_string(),
|
||||
});
|
||||
let persistent_state = Rc::new(PersistentOutputState {
|
||||
transform: Default::default(),
|
||||
scale: Default::default(),
|
||||
pos: Default::default(),
|
||||
});
|
||||
let dummy_output = Rc::new(OutputNode {
|
||||
id: state.node_ids.next(),
|
||||
global: Rc::new(WlOutputGlobal::new(
|
||||
|
|
@ -374,18 +393,16 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
drm_dev: None,
|
||||
async_event: Default::default(),
|
||||
}),
|
||||
0,
|
||||
Vec::new(),
|
||||
&backend::Mode {
|
||||
width: 0,
|
||||
height: 0,
|
||||
refresh_rate_millihz: 0,
|
||||
},
|
||||
"jay",
|
||||
"dummy-output",
|
||||
"0",
|
||||
0,
|
||||
0,
|
||||
&output_id,
|
||||
&persistent_state,
|
||||
)),
|
||||
jay_outputs: Default::default(),
|
||||
workspaces: Default::default(),
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ unsafe extern "C" fn default_client_init(
|
|||
size: usize,
|
||||
) -> *const u8 {
|
||||
extern "C" fn configure() {
|
||||
default_config::configure();
|
||||
jay_toml_config::configure();
|
||||
}
|
||||
jay_config::_private::client::init(srv_data, srv_unref, srv_handler, msg, size, configure)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,9 +164,7 @@ impl ConfigProxyHandler {
|
|||
return;
|
||||
}
|
||||
}
|
||||
let global_name = self.state.globals.name();
|
||||
let seat = WlSeatGlobal::new(global_name, name, &self.state);
|
||||
self.state.globals.add_global(&self.state, &seat);
|
||||
let seat = self.state.create_seat(name);
|
||||
self.respond(Response::GetSeat {
|
||||
seat: Seat(seat.id().raw() as _),
|
||||
});
|
||||
|
|
@ -185,13 +183,26 @@ impl ConfigProxyHandler {
|
|||
res
|
||||
}
|
||||
|
||||
fn handle_get_drm_device_connectors(&self, dev: DrmDevice) -> Result<(), CphError> {
|
||||
let dev = self.get_drm_device(dev)?;
|
||||
let mut connectors = vec![];
|
||||
for c in dev.connectors.lock().values() {
|
||||
connectors.push(Connector(c.connector.id().raw() as _));
|
||||
fn handle_get_connectors(
|
||||
&self,
|
||||
dev: Option<DrmDevice>,
|
||||
connected_only: bool,
|
||||
) -> Result<(), CphError> {
|
||||
let datas: Vec<_>;
|
||||
if let Some(dev) = dev {
|
||||
let dev = self.get_drm_device(dev)?;
|
||||
datas = dev.connectors.lock().values().cloned().collect();
|
||||
} else {
|
||||
datas = self.state.connectors.lock().values().cloned().collect();
|
||||
}
|
||||
self.respond(Response::GetDeviceConnectors { connectors });
|
||||
let connectors = datas
|
||||
.iter()
|
||||
.flat_map(|d| match (connected_only, d.connected.get()) {
|
||||
(false, _) | (true, true) => Some(Connector(d.connector.id().raw() as _)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
self.respond(Response::GetConnectors { connectors });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +213,13 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_drm_device_devnode(&self, dev: DrmDevice) -> Result<(), CphError> {
|
||||
let dev = self.get_drm_device(dev)?;
|
||||
let devnode = dev.devnode.clone().unwrap_or_default();
|
||||
self.respond(Response::GetDrmDeviceDevnode { devnode });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_drm_device_vendor(&self, dev: DrmDevice) -> Result<(), CphError> {
|
||||
let dev = self.get_drm_device(dev)?;
|
||||
let vendor = dev.vendor.clone().unwrap_or_default();
|
||||
|
|
@ -306,6 +324,35 @@ impl ConfigProxyHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_unset_env(&self, key: &str) {
|
||||
if let Some(f) = self.state.forker.get() {
|
||||
f.unsetenv(key.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_get_config_dir(&self) {
|
||||
let dir = self.state.config_dir.clone().unwrap_or_default();
|
||||
self.respond(Response::GetConfigDir { dir });
|
||||
}
|
||||
|
||||
fn handle_get_workspaces(&self) {
|
||||
let mut workspaces = vec![];
|
||||
for ws in self.state.workspaces.lock().values() {
|
||||
let id = match self.workspaces_by_name.get(&ws.name) {
|
||||
None => {
|
||||
let id = self.workspace_ids.fetch_add(1);
|
||||
let name = Rc::new(ws.name.clone());
|
||||
self.workspaces_by_name.set(name.clone(), id);
|
||||
self.workspaces_by_id.set(id, name);
|
||||
id
|
||||
}
|
||||
Some(id) => id,
|
||||
};
|
||||
workspaces.push(Workspace(id));
|
||||
}
|
||||
self.respond(Response::GetWorkspaces { workspaces });
|
||||
}
|
||||
|
||||
fn handle_program_timer(
|
||||
&self,
|
||||
timer: JayTimer,
|
||||
|
|
@ -690,6 +737,26 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_input_device_syspath(&self, device: InputDevice) -> Result<(), CphError> {
|
||||
let dev = self.get_device_handler_data(device)?;
|
||||
self.respond(Response::GetInputDeviceSyspath {
|
||||
syspath: dev.syspath.clone().unwrap_or_default(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_input_device_devnode(&self, device: InputDevice) -> Result<(), CphError> {
|
||||
let dev = self.get_device_handler_data(device)?;
|
||||
self.respond(Response::GetInputDeviceDevnode {
|
||||
devnode: dev.devnode.clone().unwrap_or_default(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_idle(&self, timeout: Duration) {
|
||||
self.state.idle.set_timeout(timeout);
|
||||
}
|
||||
|
||||
fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_connector(connector)?;
|
||||
self.respond(Response::ConnectorConnected {
|
||||
|
|
@ -749,6 +816,38 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_connector(connector)?;
|
||||
self.respond(Response::GetConnectorName {
|
||||
name: connector.name.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_output(connector)?;
|
||||
self.respond(Response::GetConnectorModel {
|
||||
model: connector.monitor_info.product.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_manufacturer(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_output(connector)?;
|
||||
self.respond(Response::GetConnectorManufacturer {
|
||||
manufacturer: connector.monitor_info.manufacturer.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_serial_number(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_output(connector)?;
|
||||
self.respond(Response::GetConnectorSerialNumber {
|
||||
serial_number: connector.monitor_info.serial_number.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
if size < 0 {
|
||||
|
|
@ -795,7 +894,7 @@ impl ConfigProxyHandler {
|
|||
fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_output(connector)?;
|
||||
self.respond(Response::ConnectorGetScale {
|
||||
scale: connector.node.global.preferred_scale.get().to_f64(),
|
||||
scale: connector.node.global.persistent.scale.get().to_f64(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -850,6 +949,13 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_get_position(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_output(connector)?;
|
||||
let (x, y) = connector.node.global.pos.get().position();
|
||||
self.respond(Response::ConnectorGetPosition { x, y });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_set_enabled(
|
||||
&self,
|
||||
connector: Connector,
|
||||
|
|
@ -1013,6 +1119,19 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_log_level(&self, level: LogLevel) {
|
||||
let level = match level {
|
||||
LogLevel::Error => Level::Error,
|
||||
LogLevel::Warn => Level::Warn,
|
||||
LogLevel::Info => Level::Info,
|
||||
LogLevel::Debug => Level::Debug,
|
||||
LogLevel::Trace => Level::Trace,
|
||||
};
|
||||
if let Some(logger) = &self.state.logger {
|
||||
logger.set_level(level);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> {
|
||||
let kb = self.get_kb(kb)?;
|
||||
kb.grab(grab);
|
||||
|
|
@ -1252,6 +1371,10 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_destroy_keymap(&self, keymap: Keymap) {
|
||||
self.keymaps.remove(&keymap);
|
||||
}
|
||||
|
||||
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
|
||||
if let Err(e) = self.handle_request_(msg) {
|
||||
log::error!("Could not handle client request: {}", ErrorFmt(e));
|
||||
|
|
@ -1401,7 +1524,7 @@ impl ConfigProxyHandler {
|
|||
}
|
||||
ClientMessage::Reload => self.handle_reload(),
|
||||
ClientMessage::GetDeviceConnectors { device } => self
|
||||
.handle_get_drm_device_connectors(device)
|
||||
.handle_get_connectors(Some(device), false)
|
||||
.wrn("get_device_connectors")?,
|
||||
ClientMessage::GetDrmDeviceSyspath { device } => self
|
||||
.handle_get_drm_device_syspath(device)
|
||||
|
|
@ -1516,6 +1639,43 @@ impl ConfigProxyHandler {
|
|||
env,
|
||||
fds,
|
||||
} => self.handle_run(prog, args, env, fds).wrn("run")?,
|
||||
ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false),
|
||||
ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap),
|
||||
ClientMessage::GetConnectorName { connector } => self
|
||||
.handle_connector_name(connector)
|
||||
.wrn("connector_name")?,
|
||||
ClientMessage::GetConnectorModel { connector } => self
|
||||
.handle_connector_model(connector)
|
||||
.wrn("connector_model")?,
|
||||
ClientMessage::GetConnectorManufacturer { connector } => self
|
||||
.handle_connector_manufacturer(connector)
|
||||
.wrn("connector_manufacturer")?,
|
||||
ClientMessage::GetConnectorSerialNumber { connector } => self
|
||||
.handle_connector_serial_number(connector)
|
||||
.wrn("connector_serial_number")?,
|
||||
ClientMessage::GetConnectors {
|
||||
device,
|
||||
connected_only,
|
||||
} => self
|
||||
.handle_get_connectors(device, connected_only)
|
||||
.wrn("get_connectors")?,
|
||||
ClientMessage::ConnectorGetPosition { connector } => self
|
||||
.handle_connector_get_position(connector)
|
||||
.wrn("connector_get_position")?,
|
||||
ClientMessage::GetConfigDir => self.handle_get_config_dir(),
|
||||
ClientMessage::GetWorkspaces => self.handle_get_workspaces(),
|
||||
ClientMessage::UnsetEnv { key } => self.handle_unset_env(key),
|
||||
ClientMessage::SetLogLevel { level } => self.handle_set_log_level(level),
|
||||
ClientMessage::GetDrmDeviceDevnode { device } => self
|
||||
.handle_get_drm_device_devnode(device)
|
||||
.wrn("get_drm_device_devnode")?,
|
||||
ClientMessage::GetInputDeviceSyspath { device } => self
|
||||
.handle_get_input_device_syspath(device)
|
||||
.wrn("get_input_device_syspath")?,
|
||||
ClientMessage::GetInputDeviceDevnode { device } => self
|
||||
.handle_get_input_device_devnode(device)
|
||||
.wrn("get_input_device_devnode")?,
|
||||
ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ impl dyn GfxFramebuffer {
|
|||
scale,
|
||||
render_hardware_cursor,
|
||||
node.has_fullscreen(),
|
||||
node.global.transform.get(),
|
||||
node.global.persistent.transform.get(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ impl JayInput {
|
|||
.map(|s| s.seat_name())
|
||||
.unwrap_or_default(),
|
||||
id: data.id.raw(),
|
||||
syspath: data.syspath.as_deref().unwrap_or_default(),
|
||||
devnode: data.devnode.as_deref().unwrap_or_default(),
|
||||
syspath: data.data.syspath.as_deref().unwrap_or_default(),
|
||||
devnode: data.data.devnode.as_deref().unwrap_or_default(),
|
||||
name: dev.name().as_str(),
|
||||
capabilities: &caps,
|
||||
accel_available: accel_profile.is_some() as _,
|
||||
|
|
|
|||
|
|
@ -92,12 +92,12 @@ impl JayRandr {
|
|||
let pos = global.pos.get();
|
||||
self.client.event(Output {
|
||||
self_id: self.id,
|
||||
scale: global.preferred_scale.get().0,
|
||||
scale: global.persistent.scale.get().to_wl(),
|
||||
width: pos.width(),
|
||||
height: pos.height(),
|
||||
x: pos.x1(),
|
||||
y: pos.y1(),
|
||||
transform: global.transform.get().to_wl(),
|
||||
transform: global.persistent.transform.get().to_wl(),
|
||||
manufacturer: &output.monitor_info.manufacturer,
|
||||
product: &output.monitor_info.product,
|
||||
serial_number: &output.monitor_info.serial_number,
|
||||
|
|
@ -217,7 +217,7 @@ impl JayRandr {
|
|||
let Some(c) = self.get_output(req.output) else {
|
||||
return Ok(());
|
||||
};
|
||||
c.node.set_preferred_scale(Scale(req.scale));
|
||||
c.node.set_preferred_scale(Scale::from_wl(req.scale));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ impl JayScreencast {
|
|||
x_off,
|
||||
y_off,
|
||||
size,
|
||||
on.global.transform.get(),
|
||||
on.global.persistent.transform.get(),
|
||||
);
|
||||
self.client.event(Ready {
|
||||
self_id: self.id,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use {
|
|||
rect::Rect,
|
||||
state::{ConnectorData, State},
|
||||
time::Time,
|
||||
tree::OutputNode,
|
||||
tree::{calculate_logical_size, OutputNode},
|
||||
utils::{
|
||||
buffd::{MsgParser, MsgParserError},
|
||||
clonecell::CloneCell,
|
||||
|
|
@ -75,12 +75,18 @@ pub struct WlOutputGlobal {
|
|||
pub pending_captures: LinkedList<Rc<ZwlrScreencopyFrameV1>>,
|
||||
pub destroyed: Cell<bool>,
|
||||
pub legacy_scale: Cell<u32>,
|
||||
pub preferred_scale: Cell<crate::scale::Scale>,
|
||||
pub persistent: Rc<PersistentOutputState>,
|
||||
}
|
||||
|
||||
pub struct PersistentOutputState {
|
||||
pub transform: Cell<Transform>,
|
||||
pub scale: Cell<crate::scale::Scale>,
|
||||
pub pos: Cell<(i32, i32)>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
pub struct OutputId {
|
||||
pub connector: String,
|
||||
pub manufacturer: String,
|
||||
pub model: String,
|
||||
pub serial_number: String,
|
||||
|
|
@ -96,33 +102,26 @@ impl WlOutputGlobal {
|
|||
name: GlobalName,
|
||||
state: &Rc<State>,
|
||||
connector: &Rc<ConnectorData>,
|
||||
x1: i32,
|
||||
modes: Vec<backend::Mode>,
|
||||
mode: &backend::Mode,
|
||||
manufacturer: &str,
|
||||
product: &str,
|
||||
serial_number: &str,
|
||||
width_mm: i32,
|
||||
height_mm: i32,
|
||||
output_id: &Rc<OutputId>,
|
||||
persistent_state: &Rc<PersistentOutputState>,
|
||||
) -> Self {
|
||||
let output_id = Rc::new(OutputId {
|
||||
manufacturer: manufacturer.to_string(),
|
||||
model: product.to_string(),
|
||||
serial_number: serial_number.to_string(),
|
||||
});
|
||||
let transform = state
|
||||
.output_transforms
|
||||
.borrow()
|
||||
.get(&output_id)
|
||||
.copied()
|
||||
.unwrap_or(Transform::None);
|
||||
let (width, height) = transform.maybe_swap((mode.width, mode.height));
|
||||
let (x, y) = persistent_state.pos.get();
|
||||
let scale = persistent_state.scale.get();
|
||||
let (width, height) = calculate_logical_size(
|
||||
(mode.width, mode.height),
|
||||
persistent_state.transform.get(),
|
||||
scale,
|
||||
);
|
||||
Self {
|
||||
name,
|
||||
state: state.clone(),
|
||||
connector: connector.clone(),
|
||||
pos: Cell::new(Rect::new_sized(x1, 0, width, height).unwrap()),
|
||||
output_id,
|
||||
pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()),
|
||||
output_id: output_id.clone(),
|
||||
mode: Cell::new(*mode),
|
||||
modes,
|
||||
node: Default::default(),
|
||||
|
|
@ -132,9 +131,8 @@ impl WlOutputGlobal {
|
|||
unused_captures: Default::default(),
|
||||
pending_captures: Default::default(),
|
||||
destroyed: Cell::new(false),
|
||||
legacy_scale: Cell::new(1),
|
||||
preferred_scale: Cell::new(crate::scale::Scale::from_int(1)),
|
||||
transform: Cell::new(transform),
|
||||
legacy_scale: Cell::new(scale.round_up()),
|
||||
persistent: persistent_state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +287,10 @@ impl WlOutputGlobal {
|
|||
|
||||
pub fn pixel_size(&self) -> (i32, i32) {
|
||||
let mode = self.mode.get();
|
||||
self.transform.get().maybe_swap((mode.width, mode.height))
|
||||
self.persistent
|
||||
.transform
|
||||
.get()
|
||||
.maybe_swap((mode.width, mode.height))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +337,7 @@ impl WlOutput {
|
|||
subpixel: SP_UNKNOWN,
|
||||
make: &self.global.output_id.manufacturer,
|
||||
model: &self.global.output_id.model,
|
||||
transform: self.global.transform.get().to_wl(),
|
||||
transform: self.global.persistent.transform.get().to_wl(),
|
||||
};
|
||||
self.client.event(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,9 +272,9 @@ impl WlSeatGlobal {
|
|||
let (x, y) = self.get_position();
|
||||
for output in self.state.root.outputs.lock().values() {
|
||||
if let Some(hc) = output.hardware_cursor.get() {
|
||||
let transform = output.global.transform.get();
|
||||
let transform = output.global.persistent.transform.get();
|
||||
let render = render | output.hardware_cursor_needs_render.take();
|
||||
let scale = output.global.preferred_scale.get();
|
||||
let scale = output.global.persistent.scale.get();
|
||||
let extents = cursor.extents_at_scale(scale);
|
||||
let (hc_width, hc_height) = hc.size();
|
||||
if render {
|
||||
|
|
|
|||
|
|
@ -357,10 +357,10 @@ impl WlSurface {
|
|||
}
|
||||
output.global.send_enter(self);
|
||||
old.global.send_leave(self);
|
||||
if old.global.preferred_scale.get() != output.global.preferred_scale.get() {
|
||||
if old.global.persistent.scale.get() != output.global.persistent.scale.get() {
|
||||
self.on_scale_change();
|
||||
}
|
||||
if old.global.transform.get() != output.global.transform.get() {
|
||||
if old.global.persistent.transform.get() != output.global.persistent.transform.get() {
|
||||
self.send_preferred_buffer_transform();
|
||||
}
|
||||
let children = self.children.borrow_mut();
|
||||
|
|
@ -459,7 +459,7 @@ impl WlSurface {
|
|||
if self.version >= TRANSFORM_SINCE {
|
||||
self.client.event(PreferredBufferTransform {
|
||||
self_id: self.id,
|
||||
transform: self.output.get().global.transform.get().to_wl() as _,
|
||||
transform: self.output.get().global.persistent.transform.get().to_wl() as _,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,15 @@ impl WpFractionalScaleV1 {
|
|||
pub fn send_preferred_scale(&self) {
|
||||
self.client.event(PreferredScale {
|
||||
self_id: self.id,
|
||||
scale: self.surface.output.get().global.preferred_scale.get().0,
|
||||
scale: self
|
||||
.surface
|
||||
.output
|
||||
.get()
|
||||
.global
|
||||
.persistent
|
||||
.scale
|
||||
.get()
|
||||
.to_wl(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ impl ZwlrScreencopyManagerV1 {
|
|||
let mode = output.global.mode.get();
|
||||
let mut rect = Rect::new_sized(0, 0, mode.width, mode.height).unwrap();
|
||||
if let Some(region) = region {
|
||||
let scale = output.global.preferred_scale.get().to_f64();
|
||||
let scale = output.global.persistent.scale.get().to_f64();
|
||||
let x1 = (region.x1() as f64 * scale).round() as i32;
|
||||
let y1 = (region.y1() as f64 * scale).round() as i32;
|
||||
let x2 = (region.x2() as f64 * scale).round() as i32;
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ testcase!();
|
|||
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
||||
let client = run.create_client().await?;
|
||||
|
||||
tassert!(client.registry.seats.is_empty());
|
||||
tassert_eq!(client.registry.seats.len(), 1);
|
||||
|
||||
let seat = run.get_seat("default")?;
|
||||
let seat = run.get_seat("new-seat")?;
|
||||
|
||||
client.sync().await;
|
||||
|
||||
tassert_eq!(client.registry.seats.len(), 1);
|
||||
tassert_eq!(client.registry.seats.len(), 2);
|
||||
|
||||
let client_seat = client.registry.seats.get(&seat.name());
|
||||
tassert!(client_seat.is_some());
|
||||
|
|
|
|||
|
|
@ -852,7 +852,7 @@ impl UsrWlBufferOwner for GuiBuffer {
|
|||
impl UsrWpFractionalScaleOwner for WindowData {
|
||||
fn preferred_scale(self: Rc<Self>, ev: &PreferredScale) {
|
||||
let mut layout = self.first_scale.replace(false);
|
||||
let scale = Scale(ev.scale);
|
||||
let scale = Scale::from_wl(ev.scale);
|
||||
layout |= self.scale.replace(scale) != scale;
|
||||
if layout {
|
||||
self.layout();
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ impl Renderer<'_> {
|
|||
let c = theme.colors.attention_requested_background.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.attention_requested_workspaces, &c, x, y);
|
||||
let scale = output.global.preferred_scale.get();
|
||||
let scale = output.global.persistent.scale.get();
|
||||
for title in &rd.titles {
|
||||
let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y);
|
||||
self.base
|
||||
|
|
|
|||
16
src/scale.rs
16
src/scale.rs
|
|
@ -5,7 +5,13 @@ const BASEF: f64 = BASE as f64;
|
|||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Scale(pub u32);
|
||||
pub struct Scale(u32);
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Scale::from_int(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
pub fn from_int(f: u32) -> Self {
|
||||
|
|
@ -23,6 +29,14 @@ impl Scale {
|
|||
pub fn round_up(self) -> u32 {
|
||||
self.0.saturating_add(BASE - 1) / BASE
|
||||
}
|
||||
|
||||
pub fn from_wl(wl: u32) -> Self {
|
||||
Self(wl)
|
||||
}
|
||||
|
||||
pub fn to_wl(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u32> for Scale {
|
||||
|
|
|
|||
18
src/state.rs
18
src/state.rs
|
|
@ -27,7 +27,7 @@ use {
|
|||
jay_seat_events::JaySeatEvents,
|
||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||
wl_drm::WlDrmGlobal,
|
||||
wl_output::OutputId,
|
||||
wl_output::{OutputId, PersistentOutputState},
|
||||
wl_seat::{SeatIds, WlSeatGlobal},
|
||||
wl_surface::{
|
||||
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
|
||||
|
|
@ -153,9 +153,10 @@ pub struct State {
|
|||
pub dma_buf_ids: DmaBufIds,
|
||||
pub drm_feedback_ids: DrmFeedbackIds,
|
||||
pub direct_scanout_enabled: Cell<bool>,
|
||||
pub output_transforms: RefCell<AHashMap<Rc<OutputId>, Transform>>,
|
||||
pub persistent_output_states: CopyHashMap<Rc<OutputId>, Rc<PersistentOutputState>>,
|
||||
pub double_click_interval_usec: Cell<u64>,
|
||||
pub double_click_distance: Cell<i32>,
|
||||
pub create_default_seat: Cell<bool>,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -215,14 +216,14 @@ pub struct InputDeviceData {
|
|||
pub id: InputDeviceId,
|
||||
pub data: Rc<DeviceHandlerData>,
|
||||
pub async_event: Rc<AsyncEvent>,
|
||||
pub syspath: Option<String>,
|
||||
pub devnode: Option<String>,
|
||||
}
|
||||
|
||||
pub struct DeviceHandlerData {
|
||||
pub seat: CloneCell<Option<Rc<WlSeatGlobal>>>,
|
||||
pub px_per_scroll_wheel: Cell<f64>,
|
||||
pub device: Rc<dyn InputDevice>,
|
||||
pub syspath: Option<String>,
|
||||
pub devnode: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ConnectorData {
|
||||
|
|
@ -771,7 +772,7 @@ impl State {
|
|||
self,
|
||||
Some(output.global.pos.get()),
|
||||
Some(rr),
|
||||
output.global.preferred_scale.get(),
|
||||
output.global.persistent.scale.get(),
|
||||
render_hw_cursor,
|
||||
);
|
||||
output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None);
|
||||
|
|
@ -924,4 +925,11 @@ impl State {
|
|||
capture.send_failed();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_seat(self: &Rc<Self>, name: &str) -> Rc<WlSeatGlobal> {
|
||||
let global_name = self.globals.name();
|
||||
let seat = WlSeatGlobal::new(global_name, name, self);
|
||||
self.globals.add_global(self, &seat);
|
||||
seat
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo},
|
||||
ifs::wl_output::WlOutputGlobal,
|
||||
ifs::wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||
state::{ConnectorData, OutputData, State},
|
||||
tree::{OutputNode, OutputRenderData},
|
||||
utils::{asyncevent::AsyncEvent, clonecell::CloneCell},
|
||||
|
|
@ -80,27 +80,45 @@ impl ConnectorHandler {
|
|||
log::info!("Connector {} connected", self.data.connector.kernel_id());
|
||||
self.data.connected.set(true);
|
||||
let name = self.state.globals.name();
|
||||
let x1 = self
|
||||
.state
|
||||
.root
|
||||
.outputs
|
||||
.lock()
|
||||
.values()
|
||||
.map(|o| o.global.pos.get().x2())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let output_id = Rc::new(OutputId {
|
||||
connector: self.data.name.clone(),
|
||||
manufacturer: info.manufacturer.clone(),
|
||||
model: info.product.clone(),
|
||||
serial_number: info.serial_number.clone(),
|
||||
});
|
||||
let desired_state = match self.state.persistent_output_states.get(&output_id) {
|
||||
Some(ds) => ds,
|
||||
_ => {
|
||||
let x1 = self
|
||||
.state
|
||||
.root
|
||||
.outputs
|
||||
.lock()
|
||||
.values()
|
||||
.map(|o| o.global.pos.get().x2())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let ds = Rc::new(PersistentOutputState {
|
||||
transform: Default::default(),
|
||||
scale: Default::default(),
|
||||
pos: Cell::new((x1, 0)),
|
||||
});
|
||||
self.state
|
||||
.persistent_output_states
|
||||
.set(output_id.clone(), ds.clone());
|
||||
ds
|
||||
}
|
||||
};
|
||||
let global = Rc::new(WlOutputGlobal::new(
|
||||
name,
|
||||
&self.state,
|
||||
&self.data,
|
||||
x1,
|
||||
info.modes.clone(),
|
||||
&info.initial_mode,
|
||||
&info.manufacturer,
|
||||
&info.product,
|
||||
&info.serial_number,
|
||||
info.width_mm,
|
||||
info.height_mm,
|
||||
&output_id,
|
||||
&desired_state,
|
||||
));
|
||||
let on = Rc::new(OutputNode {
|
||||
id: self.state.node_ids.next(),
|
||||
|
|
@ -130,8 +148,8 @@ impl ConnectorHandler {
|
|||
update_render_data_scheduled: Cell::new(false),
|
||||
hardware_cursor_needs_render: Cell::new(false),
|
||||
});
|
||||
self.state.add_output_scale(on.global.preferred_scale.get());
|
||||
let mode = info.initial_mode;
|
||||
self.state
|
||||
.add_output_scale(on.global.persistent.scale.get());
|
||||
let output_data = Rc::new(OutputData {
|
||||
connector: self.data.clone(),
|
||||
monitor_info: info,
|
||||
|
|
@ -140,8 +158,11 @@ impl ConnectorHandler {
|
|||
self.state.outputs.set(self.id, output_data);
|
||||
if self.state.outputs.len() == 1 {
|
||||
let seats = self.state.globals.seats.lock();
|
||||
let pos = global.pos.get();
|
||||
let x = (pos.x1() + pos.x2()) / 2;
|
||||
let y = (pos.y1() + pos.y2()) / 2;
|
||||
for seat in seats.values() {
|
||||
seat.set_position(x1 + mode.width / 2, mode.height / 2);
|
||||
seat.set_position(x, y);
|
||||
}
|
||||
}
|
||||
global.node.set(Some(on.clone()));
|
||||
|
|
@ -277,7 +298,7 @@ impl ConnectorHandler {
|
|||
dev.connectors.remove(&self.id);
|
||||
}
|
||||
self.state
|
||||
.remove_output_scale(on.global.preferred_scale.get());
|
||||
.remove_output_scale(on.global.persistent.scale.get());
|
||||
let _ = self.state.remove_global(&*global);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,21 @@ use {
|
|||
tasks::udev_utils::{udev_props, UdevProps},
|
||||
utils::asyncevent::AsyncEvent,
|
||||
},
|
||||
jay_config::_private::DEFAULT_SEAT_NAME,
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
|
||||
let props = match dev.dev_t() {
|
||||
None => UdevProps::default(),
|
||||
Some(dev_t) => udev_props(dev_t, 3),
|
||||
};
|
||||
let data = Rc::new(DeviceHandlerData {
|
||||
seat: Default::default(),
|
||||
px_per_scroll_wheel: Cell::new(PX_PER_SCROLL),
|
||||
device: dev.clone(),
|
||||
syspath: props.syspath,
|
||||
devnode: props.devnode,
|
||||
});
|
||||
let ae = Rc::new(AsyncEvent::default());
|
||||
let oh = DeviceHandler {
|
||||
|
|
@ -23,10 +30,6 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
|
|||
ae: ae.clone(),
|
||||
};
|
||||
let handler = state.eng.spawn(oh.handle());
|
||||
let props = match dev.dev_t() {
|
||||
None => UdevProps::default(),
|
||||
Some(dev_t) => udev_props(dev_t, 3),
|
||||
};
|
||||
state.input_device_handlers.borrow_mut().insert(
|
||||
dev.id(),
|
||||
InputDeviceData {
|
||||
|
|
@ -34,8 +37,6 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
|
|||
id: dev.id(),
|
||||
data,
|
||||
async_event: ae,
|
||||
syspath: props.syspath,
|
||||
devnode: props.devnode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -53,6 +54,12 @@ impl DeviceHandler {
|
|||
let ae = self.ae.clone();
|
||||
self.dev.on_change(Rc::new(move || ae.trigger()));
|
||||
}
|
||||
for seat in self.state.globals.seats.lock().values() {
|
||||
if seat.seat_name() == DEFAULT_SEAT_NAME {
|
||||
self.data.seat.set(Some(seat.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(config) = self.state.config.get() {
|
||||
config.new_input_device(self.dev.id());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ use {
|
|||
},
|
||||
utils::{
|
||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
||||
linkedlist::LinkedList, scroller::Scroller,
|
||||
linkedlist::LinkedList, scroller::Scroller, transform_ext::TransformExt,
|
||||
},
|
||||
wire::{JayOutputId, JayScreencastId},
|
||||
},
|
||||
|
|
@ -120,7 +120,7 @@ impl OutputNode {
|
|||
}
|
||||
|
||||
pub fn set_preferred_scale(self: &Rc<Self>, scale: Scale) {
|
||||
let old_scale = self.global.preferred_scale.replace(scale);
|
||||
let old_scale = self.global.persistent.scale.replace(scale);
|
||||
if scale == old_scale {
|
||||
return;
|
||||
}
|
||||
|
|
@ -161,7 +161,7 @@ impl OutputNode {
|
|||
let font = self.state.theme.font.borrow_mut();
|
||||
let theme = &self.state.theme;
|
||||
let th = theme.sizes.title_height.get();
|
||||
let scale = self.global.preferred_scale.get();
|
||||
let scale = self.global.persistent.scale.get();
|
||||
let scale = if scale != 1 {
|
||||
Some(scale.to_f64())
|
||||
} else {
|
||||
|
|
@ -388,7 +388,7 @@ impl OutputNode {
|
|||
}
|
||||
|
||||
pub fn update_mode(self: &Rc<Self>, mode: Mode) {
|
||||
self.update_mode_and_transform(mode, self.global.transform.get());
|
||||
self.update_mode_and_transform(mode, self.global.persistent.transform.get());
|
||||
}
|
||||
|
||||
pub fn update_transform(self: &Rc<Self>, transform: Transform) {
|
||||
|
|
@ -397,17 +397,13 @@ impl OutputNode {
|
|||
|
||||
pub fn update_mode_and_transform(self: &Rc<Self>, mode: Mode, transform: Transform) {
|
||||
let old_mode = self.global.mode.get();
|
||||
let old_transform = self.global.transform.get();
|
||||
let old_transform = self.global.persistent.transform.get();
|
||||
if (old_mode, old_transform) == (mode, transform) {
|
||||
return;
|
||||
}
|
||||
let (old_width, old_height) = self.global.pixel_size();
|
||||
self.global.mode.set(mode);
|
||||
self.state
|
||||
.output_transforms
|
||||
.borrow_mut()
|
||||
.insert(self.global.output_id.clone(), transform);
|
||||
self.global.transform.set(transform);
|
||||
self.global.persistent.transform.set(transform);
|
||||
let (new_width, new_height) = self.global.pixel_size();
|
||||
self.change_extents_(&self.calculate_extents());
|
||||
|
||||
|
|
@ -436,18 +432,18 @@ impl OutputNode {
|
|||
}
|
||||
|
||||
fn calculate_extents(&self) -> Rect {
|
||||
let (mut width, mut height) = self.global.pixel_size();
|
||||
let scale = self.global.preferred_scale.get();
|
||||
if scale != 1 {
|
||||
let scale = scale.to_f64();
|
||||
width = (width as f64 / scale).round() as _;
|
||||
height = (height as f64 / scale).round() as _;
|
||||
}
|
||||
let mode = self.global.mode.get();
|
||||
let (width, height) = calculate_logical_size(
|
||||
(mode.width, mode.height),
|
||||
self.global.persistent.transform.get(),
|
||||
self.global.persistent.scale.get(),
|
||||
);
|
||||
let pos = self.global.pos.get();
|
||||
pos.with_size(width, height).unwrap()
|
||||
}
|
||||
|
||||
fn change_extents_(self: &Rc<Self>, rect: &Rect) {
|
||||
self.global.persistent.pos.set((rect.x1(), rect.y1()));
|
||||
self.global.pos.set(*rect);
|
||||
self.state.root.update_extents();
|
||||
self.schedule_update_render_data();
|
||||
|
|
@ -766,3 +762,17 @@ impl Node for OutputNode {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_logical_size(
|
||||
mode: (i32, i32),
|
||||
transform: Transform,
|
||||
scale: crate::scale::Scale,
|
||||
) -> (i32, i32) {
|
||||
let (mut width, mut height) = transform.maybe_swap(mode);
|
||||
if scale != 1 {
|
||||
let scale = scale.to_f64();
|
||||
width = (width as f64 / scale).round() as _;
|
||||
height = (height as f64 / scale).round() as _;
|
||||
}
|
||||
(width, height)
|
||||
}
|
||||
|
|
|
|||
22
toml-config/Cargo.toml
Normal file
22
toml-config/Cargo.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "jay-toml-config"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
jay-config = { path = "../jay-config" }
|
||||
log = "0.4.14"
|
||||
thiserror = "1.0.57"
|
||||
error_reporter = "1.0.0"
|
||||
phf = { version = "0.11.2", features = ["macros"] }
|
||||
indexmap = "2.2.5"
|
||||
bstr = { version = "1.9.1", default-features = false }
|
||||
ahash = "0.8.11"
|
||||
|
||||
[dev-dependencies]
|
||||
simplelog = { version = "0.12.2", features = ["test"] }
|
||||
serde_json = "1.0.114"
|
||||
walkdir = "2.5.0"
|
||||
307
toml-config/src/config.rs
Normal file
307
toml-config/src/config.rs
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
mod context;
|
||||
pub mod error;
|
||||
mod extractor;
|
||||
mod keysyms;
|
||||
mod parser;
|
||||
mod parsers;
|
||||
mod spanned;
|
||||
mod value;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
parsers::config::{ConfigParser, ConfigParserError},
|
||||
},
|
||||
toml::{self},
|
||||
},
|
||||
jay_config::{
|
||||
input::acceleration::AccelProfile,
|
||||
keyboard::{Keymap, ModifiedKeySym},
|
||||
logging::LogLevel,
|
||||
status::MessageFormat,
|
||||
theme::Color,
|
||||
video::{GfxApi, Transform},
|
||||
Axis, Direction,
|
||||
},
|
||||
std::{
|
||||
error::Error,
|
||||
fmt::{Display, Formatter},
|
||||
time::Duration,
|
||||
},
|
||||
thiserror::Error,
|
||||
toml::toml_parser,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SimpleCommand {
|
||||
Close,
|
||||
DisablePointerConstraint,
|
||||
Focus(Direction),
|
||||
FocusParent,
|
||||
Move(Direction),
|
||||
None,
|
||||
Quit,
|
||||
ReloadConfigSo,
|
||||
ReloadConfigToml,
|
||||
Split(Axis),
|
||||
ToggleFloating,
|
||||
ToggleFullscreen,
|
||||
ToggleMono,
|
||||
ToggleSplit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
ConfigureConnector { con: ConfigConnector },
|
||||
ConfigureDirectScanout { enabled: bool },
|
||||
ConfigureDrmDevice { dev: ConfigDrmDevice },
|
||||
ConfigureIdle { idle: Duration },
|
||||
ConfigureInput { input: Input },
|
||||
ConfigureOutput { out: Output },
|
||||
Exec { exec: Exec },
|
||||
Multi { actions: Vec<Action> },
|
||||
SetEnv { env: Vec<(String, String)> },
|
||||
SetGfxApi { api: GfxApi },
|
||||
SetKeymap { map: ConfigKeymap },
|
||||
SetLogLevel { level: LogLevel },
|
||||
SetRenderDevice { dev: DrmDeviceMatch },
|
||||
SetStatus { status: Option<Status> },
|
||||
SetTheme { theme: Box<Theme> },
|
||||
ShowWorkspace { name: String },
|
||||
SimpleCommand { cmd: SimpleCommand },
|
||||
SwitchToVt { num: u32 },
|
||||
UnsetEnv { env: Vec<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Theme {
|
||||
pub attention_requested_bg_color: Option<Color>,
|
||||
pub bg_color: Option<Color>,
|
||||
pub bar_bg_color: Option<Color>,
|
||||
pub bar_status_text_color: Option<Color>,
|
||||
pub border_color: Option<Color>,
|
||||
pub captured_focused_title_bg_color: Option<Color>,
|
||||
pub captured_unfocused_title_bg_color: Option<Color>,
|
||||
pub focused_inactive_title_bg_color: Option<Color>,
|
||||
pub focused_inactive_title_text_color: Option<Color>,
|
||||
pub focused_title_bg_color: Option<Color>,
|
||||
pub focused_title_text_color: Option<Color>,
|
||||
pub separator_color: Option<Color>,
|
||||
pub unfocused_title_bg_color: Option<Color>,
|
||||
pub unfocused_title_text_color: Option<Color>,
|
||||
pub border_width: Option<i32>,
|
||||
pub title_height: Option<i32>,
|
||||
pub font: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Status {
|
||||
pub format: MessageFormat,
|
||||
pub exec: Exec,
|
||||
pub separator: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OutputMatch {
|
||||
Any(Vec<OutputMatch>),
|
||||
All {
|
||||
name: Option<String>,
|
||||
connector: Option<String>,
|
||||
serial_number: Option<String>,
|
||||
manufacturer: Option<String>,
|
||||
model: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmDeviceMatch {
|
||||
Any(Vec<DrmDeviceMatch>),
|
||||
All {
|
||||
name: Option<String>,
|
||||
syspath: Option<String>,
|
||||
vendor: Option<u32>,
|
||||
vendor_name: Option<String>,
|
||||
model: Option<u32>,
|
||||
model_name: Option<String>,
|
||||
devnode: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mode {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub refresh_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} x {}", self.width, self.height)?;
|
||||
if let Some(rr) = self.refresh_rate {
|
||||
write!(f, " @ {}", rr)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Output {
|
||||
pub name: Option<String>,
|
||||
pub match_: OutputMatch,
|
||||
pub x: Option<i32>,
|
||||
pub y: Option<i32>,
|
||||
pub scale: Option<f64>,
|
||||
pub transform: Option<Transform>,
|
||||
pub mode: Option<Mode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConnectorMatch {
|
||||
Any(Vec<ConnectorMatch>),
|
||||
All { connector: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InputMatch {
|
||||
Any(Vec<InputMatch>),
|
||||
All {
|
||||
tag: Option<String>,
|
||||
name: Option<String>,
|
||||
syspath: Option<String>,
|
||||
devnode: Option<String>,
|
||||
is_keyboard: Option<bool>,
|
||||
is_pointer: Option<bool>,
|
||||
is_touch: Option<bool>,
|
||||
is_tablet_tool: Option<bool>,
|
||||
is_tablet_pad: Option<bool>,
|
||||
is_gesture: Option<bool>,
|
||||
is_switch: Option<bool>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Input {
|
||||
pub tag: Option<String>,
|
||||
pub match_: InputMatch,
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
pub accel_speed: Option<f64>,
|
||||
pub tap_enabled: Option<bool>,
|
||||
pub tap_drag_enabled: Option<bool>,
|
||||
pub tap_drag_lock_enabled: Option<bool>,
|
||||
pub left_handed: Option<bool>,
|
||||
pub natural_scrolling: Option<bool>,
|
||||
pub px_per_wheel_scroll: Option<f64>,
|
||||
pub transform_matrix: Option<[[f64; 2]; 2]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Exec {
|
||||
pub prog: String,
|
||||
pub args: Vec<String>,
|
||||
pub envs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigConnector {
|
||||
pub match_: ConnectorMatch,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigDrmDevice {
|
||||
pub name: Option<String>,
|
||||
pub match_: DrmDeviceMatch,
|
||||
pub gfx_api: Option<GfxApi>,
|
||||
pub direct_scanout_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConfigKeymap {
|
||||
Named(String),
|
||||
Literal(Keymap),
|
||||
Defined { name: String, map: Keymap },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub keymap: Option<ConfigKeymap>,
|
||||
pub shortcuts: Vec<(ModifiedKeySym, Action)>,
|
||||
pub on_graphics_initialized: Option<Action>,
|
||||
pub on_idle: Option<Action>,
|
||||
pub status: Option<Status>,
|
||||
pub connectors: Vec<ConfigConnector>,
|
||||
pub outputs: Vec<Output>,
|
||||
pub workspace_capture: bool,
|
||||
pub env: Vec<(String, String)>,
|
||||
pub on_startup: Option<Action>,
|
||||
pub keymaps: Vec<ConfigKeymap>,
|
||||
pub log_level: Option<LogLevel>,
|
||||
pub theme: Theme,
|
||||
pub gfx_api: Option<GfxApi>,
|
||||
pub direct_scanout_enabled: Option<bool>,
|
||||
pub drm_devices: Vec<ConfigDrmDevice>,
|
||||
pub render_device: Option<DrmDeviceMatch>,
|
||||
pub inputs: Vec<Input>,
|
||||
pub idle: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("Could not parse the toml document")]
|
||||
Toml(#[from] toml_parser::ParserError),
|
||||
#[error("Could not interpret the toml as a config document")]
|
||||
Parser(#[from] ConfigParserError),
|
||||
}
|
||||
|
||||
pub fn parse_config<F>(input: &[u8], handle_error: F) -> Option<Config>
|
||||
where
|
||||
F: FnOnce(&dyn Error),
|
||||
{
|
||||
let cx = Context {
|
||||
input,
|
||||
used: Default::default(),
|
||||
};
|
||||
macro_rules! fatal {
|
||||
($e:expr) => {{
|
||||
let e = ConfigError::from($e.value);
|
||||
let e = cx.error2($e.span, e);
|
||||
handle_error(&e);
|
||||
return None;
|
||||
}};
|
||||
}
|
||||
let toml = match toml_parser::parse(input, &cx) {
|
||||
Ok(t) => t,
|
||||
Err(e) => fatal!(e),
|
||||
};
|
||||
let config = match toml.parse(&mut ConfigParser(&cx)) {
|
||||
Ok(c) => c,
|
||||
Err(e) => fatal!(e),
|
||||
};
|
||||
let used = cx.used.take();
|
||||
macro_rules! check_defined {
|
||||
($name:expr, $used:ident, $defined:ident) => {
|
||||
for spanned in &used.$used {
|
||||
if !used.$defined.contains(spanned) {
|
||||
log::warn!(
|
||||
"{} {} used but not defined: {}",
|
||||
$name,
|
||||
spanned.value,
|
||||
cx.error3(spanned.span),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
check_defined!("Keymap", keymaps, defined_keymaps);
|
||||
check_defined!("DRM device", drm_devices, defined_drm_devices);
|
||||
check_defined!("Output", outputs, defined_outputs);
|
||||
check_defined!("Input", inputs, defined_inputs);
|
||||
Some(config)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_config_parses() {
|
||||
let input = include_bytes!("default-config.toml");
|
||||
parse_config(input, |_| ()).unwrap();
|
||||
}
|
||||
65
toml-config/src/config/context.rs
Normal file
65
toml-config/src/config/context.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use {
|
||||
crate::{
|
||||
config::error::SpannedError,
|
||||
toml::{
|
||||
toml_parser::{ErrorHandler, ParserError},
|
||||
toml_span::{Span, Spanned},
|
||||
},
|
||||
},
|
||||
ahash::AHashSet,
|
||||
error_reporter::Report,
|
||||
std::{cell::RefCell, convert::Infallible, error::Error},
|
||||
};
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub input: &'a [u8],
|
||||
pub used: RefCell<Used>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Used {
|
||||
pub outputs: Vec<Spanned<String>>,
|
||||
pub inputs: Vec<Spanned<String>>,
|
||||
pub drm_devices: Vec<Spanned<String>>,
|
||||
pub keymaps: Vec<Spanned<String>>,
|
||||
pub defined_outputs: AHashSet<Spanned<String>>,
|
||||
pub defined_inputs: AHashSet<Spanned<String>>,
|
||||
pub defined_drm_devices: AHashSet<Spanned<String>>,
|
||||
pub defined_keymaps: AHashSet<Spanned<String>>,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn error<E: Error>(&self, cause: Spanned<E>) -> SpannedError<'a, E> {
|
||||
self.error2(cause.span, cause.value)
|
||||
}
|
||||
|
||||
pub fn error2<E: Error>(&self, span: Span, cause: E) -> SpannedError<'a, E> {
|
||||
SpannedError {
|
||||
input: self.input.into(),
|
||||
span,
|
||||
cause: Some(cause),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error3(&self, span: Span) -> SpannedError<'a, Infallible> {
|
||||
SpannedError {
|
||||
input: self.input.into(),
|
||||
span,
|
||||
cause: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ErrorHandler for Context<'a> {
|
||||
fn handle(&self, err: Spanned<ParserError>) {
|
||||
log::warn!("{}", Report::new(self.error(err)));
|
||||
}
|
||||
|
||||
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
|
||||
log::warn!("{}", Report::new(self.error(err)));
|
||||
log::info!(
|
||||
"Previous definition here: {}",
|
||||
Report::new(self.error3(prev))
|
||||
);
|
||||
}
|
||||
}
|
||||
89
toml-config/src/config/error.rs
Normal file
89
toml-config/src/config/error.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use {
|
||||
crate::toml::toml_span::Span,
|
||||
bstr::ByteSlice,
|
||||
error_reporter::Report,
|
||||
std::{
|
||||
borrow::Cow,
|
||||
error::Error,
|
||||
fmt::{Display, Formatter},
|
||||
ops::Deref,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpannedError<'a, E> {
|
||||
pub input: Cow<'a, [u8]>,
|
||||
pub span: Span,
|
||||
pub cause: Option<E>,
|
||||
}
|
||||
|
||||
impl<'a, E: Error> Display for SpannedError<'a, E> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let original = self.input.deref();
|
||||
let span = self.span;
|
||||
|
||||
let (line, column) = translate_position(original, span.lo);
|
||||
let line_num = line + 1;
|
||||
let col_num = column + 1;
|
||||
let gutter = line_num.to_string().len();
|
||||
let content = original
|
||||
.split(|c| *c == b'\n')
|
||||
.nth(line)
|
||||
.expect("valid line number");
|
||||
|
||||
if let Some(cause) = &self.cause {
|
||||
write!(f, "{}: ", Report::new(cause))?;
|
||||
}
|
||||
writeln!(f, "At line {}, column {}:", line_num, col_num)?;
|
||||
for _ in 0..=gutter {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
writeln!(f, "|")?;
|
||||
write!(f, "{} | ", line_num)?;
|
||||
writeln!(f, "{}", content.as_bstr())?;
|
||||
for _ in 0..=gutter {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "|")?;
|
||||
for _ in 0..=column {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "^")?;
|
||||
for _ in (span.lo + 1)..(span.hi.min(span.lo + content.len() - column)) {
|
||||
write!(f, "^")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Error> Error for SpannedError<'a, E> {}
|
||||
|
||||
fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
|
||||
if input.is_empty() {
|
||||
return (0, index);
|
||||
}
|
||||
|
||||
let safe_index = index.min(input.len() - 1);
|
||||
let column_offset = index - safe_index;
|
||||
let index = safe_index;
|
||||
|
||||
let nl = input[0..index]
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.find(|(_, b)| **b == b'\n')
|
||||
.map(|(nl, _)| index - nl - 1);
|
||||
let line_start = match nl {
|
||||
Some(nl) => nl + 1,
|
||||
None => 0,
|
||||
};
|
||||
let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
|
||||
|
||||
let column = std::str::from_utf8(&input[line_start..=index])
|
||||
.map(|s| s.chars().count() - 1)
|
||||
.unwrap_or_else(|_| index - line_start);
|
||||
let column = column + column_offset;
|
||||
|
||||
(line, column)
|
||||
}
|
||||
281
toml-config/src/config/extractor.rs
Normal file
281
toml-config/src/config/extractor.rs
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
use {
|
||||
crate::{
|
||||
config::context::Context,
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
ahash::AHashSet,
|
||||
error_reporter::Report,
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct Extractor<'v> {
|
||||
cx: &'v Context<'v>,
|
||||
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
used: Vec<&'static str>,
|
||||
log_unused: bool,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<'v> Extractor<'v> {
|
||||
pub fn new(
|
||||
cx: &'v Context<'v>,
|
||||
span: Span,
|
||||
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
table,
|
||||
used: Default::default(),
|
||||
log_unused: true,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&mut self, name: &'static str) -> Option<&'v Spanned<Value>> {
|
||||
let v = self.table.get(name);
|
||||
if v.is_some() {
|
||||
self.used.push(name);
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn ignore_unused(&mut self) {
|
||||
self.log_unused = false;
|
||||
}
|
||||
|
||||
pub fn extract<E: Extractable<'v>, U>(&mut self, e: E) -> Result<E::Output, Spanned<U>>
|
||||
where
|
||||
ExtractorError: Into<U>,
|
||||
{
|
||||
e.extract(self).map_err(|e| e.map(|e| e.into()))
|
||||
}
|
||||
|
||||
pub fn extract_or_ignore<E: Extractable<'v>, U>(
|
||||
&mut self,
|
||||
e: E,
|
||||
) -> Result<E::Output, Spanned<U>>
|
||||
where
|
||||
ExtractorError: Into<U>,
|
||||
{
|
||||
let res = self.extract(e);
|
||||
if res.is_err() {
|
||||
self.ignore_unused();
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> Drop for Extractor<'v> {
|
||||
fn drop(&mut self) {
|
||||
if !self.log_unused {
|
||||
return;
|
||||
}
|
||||
if self.used.len() == self.table.len() {
|
||||
return;
|
||||
}
|
||||
let used: AHashSet<_> = self.used.iter().copied().collect();
|
||||
for key in self.table.keys() {
|
||||
if !used.contains(key.value.as_str()) {
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Ignoring unknown key {0}")]
|
||||
struct Err<'a>(&'a str);
|
||||
let err = self.cx.error2(key.span, Err(&key.value));
|
||||
log::warn!("{}", Report::new(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Eq, PartialEq)]
|
||||
pub enum ExtractorError {
|
||||
#[error("Missing field {0}")]
|
||||
MissingField(&'static str),
|
||||
#[error("Expected {0} but found {1}")]
|
||||
Expected(&'static str, &'static str),
|
||||
#[error("Value must fit in a u32")]
|
||||
U32,
|
||||
#[error("Value must fit in a u64")]
|
||||
U64,
|
||||
#[error("Value must fit in a i32")]
|
||||
I32,
|
||||
}
|
||||
|
||||
pub trait Extractable<'v> {
|
||||
type Output;
|
||||
|
||||
fn extract(
|
||||
self,
|
||||
extractor: &mut Extractor<'v>,
|
||||
) -> Result<Self::Output, Spanned<ExtractorError>>;
|
||||
}
|
||||
|
||||
impl<'v, T, F> Extractable<'v> for F
|
||||
where
|
||||
F: FnOnce(&mut Extractor<'v>) -> Result<T, Spanned<ExtractorError>>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn extract(
|
||||
self,
|
||||
extractor: &mut Extractor<'v>,
|
||||
) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||
self(extractor)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn val(
|
||||
name: &'static str,
|
||||
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<&'v Value>, Spanned<ExtractorError>>
|
||||
{
|
||||
move |extractor: &mut Extractor| match extractor.get(name) {
|
||||
None => Err(ExtractorError::MissingField(name).spanned(extractor.span)),
|
||||
Some(v) => Ok(v.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ty {
|
||||
($f:ident, $lt:lifetime, $ty:ident, $ret:ty, $v:ident, $map:expr, $name:expr) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $f(
|
||||
name: &'static str,
|
||||
) -> impl for<$lt> FnOnce(&mut Extractor<$lt>) -> Result<Spanned<$ret>, Spanned<ExtractorError>> {
|
||||
move |extractor: &mut Extractor| {
|
||||
val(name)(extractor).and_then(|v| match v.value {
|
||||
Value::$ty($v) => Ok($map.spanned(v.span)),
|
||||
_ => Err(ExtractorError::Expected($name, v.value.name()).spanned(v.span)),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ty!(str, 'a, String, &'a str, v, v.as_str(), "a string");
|
||||
ty!(int, 'a, Integer, i64, v, *v, "an integer");
|
||||
ty!(flt, 'a, Float, f64, v, *v, "a float");
|
||||
ty!(bol, 'a, Boolean, bool, v, *v, "a boolean");
|
||||
ty!(arr, 'a, Array, &'a [Spanned<Value>], v, &**v, "an array");
|
||||
ty!(tbl, 'a, Table, &'a IndexMap<Spanned<String>, Spanned<Value>>, v, v, "a table");
|
||||
|
||||
pub fn fltorint(
|
||||
name: &'static str,
|
||||
) -> impl for<'a> FnOnce(&mut Extractor<'a>) -> Result<Spanned<f64>, Spanned<ExtractorError>> {
|
||||
move |extractor: &mut Extractor| {
|
||||
val(name)(extractor).and_then(|v| match *v.value {
|
||||
Value::Float(f) => Ok(f.spanned(v.span)),
|
||||
Value::Integer(i) => Ok((i as f64).spanned(v.span)),
|
||||
_ => Err(
|
||||
ExtractorError::Expected("a float or an integer", v.value.name()).spanned(v.span),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! int {
|
||||
($f:ident, $ty:ident, $err:ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $f(
|
||||
name: &'static str,
|
||||
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<$ty>, Spanned<ExtractorError>> {
|
||||
move |extractor: &mut Extractor| {
|
||||
int(name)(extractor).and_then(|v| {
|
||||
v.value
|
||||
.try_into()
|
||||
.map(|n: $ty| n.spanned(v.span))
|
||||
.map_err(|_| ExtractorError::$err.spanned(v.span))
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int!(n32, u32, U32);
|
||||
int!(n64, u64, U64);
|
||||
int!(s32, i32, I32);
|
||||
|
||||
pub fn recover<F>(f: F) -> Recover<F> {
|
||||
Recover(f)
|
||||
}
|
||||
|
||||
pub struct Recover<E>(E);
|
||||
|
||||
impl<'v, E> Extractable<'v> for Recover<E>
|
||||
where
|
||||
E: Extractable<'v>,
|
||||
<E as Extractable<'v>>::Output: Default,
|
||||
{
|
||||
type Output = <E as Extractable<'v>>::Output;
|
||||
|
||||
fn extract(
|
||||
self,
|
||||
extractor: &mut Extractor<'v>,
|
||||
) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||
match self.0.extract(extractor) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
log::warn!("{}", extractor.cx.error(e));
|
||||
Ok(<E as Extractable<'v>>::Output::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opt<F>(f: F) -> Opt<F> {
|
||||
Opt(f)
|
||||
}
|
||||
|
||||
pub struct Opt<E>(E);
|
||||
|
||||
impl<'v, E> Extractable<'v> for Opt<E>
|
||||
where
|
||||
E: Extractable<'v>,
|
||||
{
|
||||
type Output = Option<<E as Extractable<'v>>::Output>;
|
||||
|
||||
fn extract(
|
||||
self,
|
||||
extractor: &mut Extractor<'v>,
|
||||
) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||
match self.0.extract(extractor) {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(e) if matches!(e.value, ExtractorError::MissingField(_)) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuples {
|
||||
($($idx:tt: $name:ident,)*) => {
|
||||
impl<'v, $($name,)*> Extractable<'v> for ($($name,)*)
|
||||
where $($name: Extractable<'v>,)*
|
||||
{
|
||||
type Output = ($($name::Output,)*);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn extract(self, extractor: &mut Extractor<'v>) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||
$(
|
||||
let $name = self.$idx.extract(extractor);
|
||||
)*
|
||||
Ok((
|
||||
$(
|
||||
$name?,
|
||||
)*
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tuples!(0:T0,);
|
||||
tuples!(0:T0,1:T1,);
|
||||
tuples!(0:T0,1:T1,2:T2,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,);
|
||||
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9,);
|
||||
2580
toml-config/src/config/keysyms.rs
Normal file
2580
toml-config/src/config/keysyms.rs
Normal file
File diff suppressed because it is too large
Load diff
118
toml-config/src/config/parser.rs
Normal file
118
toml-config/src/config/parser.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use {
|
||||
crate::toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
std::{
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum DataType {
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Boolean,
|
||||
Array,
|
||||
Table,
|
||||
}
|
||||
|
||||
impl Display for DataType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
DataType::String => "a string",
|
||||
DataType::Integer => "an integer",
|
||||
DataType::Float => "a float",
|
||||
DataType::Boolean => "a bool",
|
||||
DataType::Array => "an array",
|
||||
DataType::Table => "a table",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataTypes(&'static [DataType]);
|
||||
|
||||
impl Display for DataTypes {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let d = self.0;
|
||||
match d.len() {
|
||||
0 => Ok(()),
|
||||
1 => d[0].fmt(f),
|
||||
2 => write!(f, "{} or {}", d[0], d[1]),
|
||||
_ => {
|
||||
let mut first = true;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..d.len() - 1 {
|
||||
if !first {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
first = false;
|
||||
d[i].fmt(f)?;
|
||||
}
|
||||
write!(f, ", or {}", d[d.len() - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnexpectedDataType(&'static [DataType], DataType);
|
||||
|
||||
impl Display for UnexpectedDataType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Expected {} but found {}", DataTypes(self.0), self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UnexpectedDataType {}
|
||||
|
||||
pub type ParseResult<P> = Result<<P as Parser>::Value, Spanned<<P as Parser>::Error>>;
|
||||
|
||||
pub trait Parser {
|
||||
type Value;
|
||||
type Error: From<UnexpectedDataType>;
|
||||
|
||||
const EXPECTED: &'static [DataType];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let _ = string;
|
||||
expected(self, span, DataType::String)
|
||||
}
|
||||
|
||||
fn parse_integer(&mut self, span: Span, integer: i64) -> ParseResult<Self> {
|
||||
let _ = integer;
|
||||
expected(self, span, DataType::Integer)
|
||||
}
|
||||
|
||||
fn parse_float(&mut self, span: Span, float: f64) -> ParseResult<Self> {
|
||||
let _ = float;
|
||||
expected(self, span, DataType::Float)
|
||||
}
|
||||
|
||||
fn parse_bool(&mut self, span: Span, bool: bool) -> ParseResult<Self> {
|
||||
let _ = bool;
|
||||
expected(self, span, DataType::Boolean)
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let _ = array;
|
||||
expected(self, span, DataType::Array)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let _ = table;
|
||||
expected(self, span, DataType::Table)
|
||||
}
|
||||
}
|
||||
|
||||
fn expected<P: Parser + ?Sized>(_p: &P, span: Span, actual: DataType) -> ParseResult<P> {
|
||||
Err(P::Error::from(UnexpectedDataType(P::EXPECTED, actual)).spanned(span))
|
||||
}
|
||||
48
toml-config/src/config/parsers.rs
Normal file
48
toml-config/src/config/parsers.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use {
|
||||
crate::{
|
||||
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
toml::toml_span::Span,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub mod action;
|
||||
mod color;
|
||||
pub mod config;
|
||||
mod connector;
|
||||
mod connector_match;
|
||||
mod drm_device;
|
||||
mod drm_device_match;
|
||||
mod env;
|
||||
pub mod exec;
|
||||
mod gfx_api;
|
||||
mod idle;
|
||||
mod input;
|
||||
mod input_match;
|
||||
pub mod keymap;
|
||||
mod log_level;
|
||||
mod mode;
|
||||
pub mod modified_keysym;
|
||||
mod output;
|
||||
mod output_match;
|
||||
pub mod shortcuts;
|
||||
mod status;
|
||||
mod theme;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StringParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
}
|
||||
|
||||
pub struct StringParser;
|
||||
|
||||
impl Parser for StringParser {
|
||||
type Value = String;
|
||||
type Error = StringParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||
|
||||
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
|
||||
Ok(string.to_string())
|
||||
}
|
||||
}
|
||||
308
toml-config/src/config/parsers/action.rs
Normal file
308
toml-config/src/config/parsers/action.rs
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{arr, bol, n32, opt, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
connector::{ConnectorParser, ConnectorParserError},
|
||||
drm_device::{DrmDeviceParser, DrmDeviceParserError},
|
||||
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
|
||||
env::{EnvParser, EnvParserError},
|
||||
exec::{ExecParser, ExecParserError},
|
||||
gfx_api::{GfxApiParser, GfxApiParserError},
|
||||
idle::{IdleParser, IdleParserError},
|
||||
input::{InputParser, InputParserError},
|
||||
keymap::{KeymapParser, KeymapParserError},
|
||||
log_level::{LogLevelParser, LogLevelParserError},
|
||||
output::{OutputParser, OutputParserError},
|
||||
status::{StatusParser, StatusParserError},
|
||||
theme::{ThemeParser, ThemeParserError},
|
||||
StringParser, StringParserError,
|
||||
},
|
||||
Action,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::Axis::{Horizontal, Vertical},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ActionParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
StringParser(#[from] StringParserError),
|
||||
#[error("Unknown type {0}")]
|
||||
UnknownType(String),
|
||||
#[error("Unknown simple action {0}")]
|
||||
UnknownSimpleAction(String),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error("Could not parse the exec action")]
|
||||
Exec(#[from] ExecParserError),
|
||||
#[error("Could not parse the configure-connector action")]
|
||||
ConfigureConnector(#[from] ConnectorParserError),
|
||||
#[error("Could not parse the configure-input action")]
|
||||
ConfigureInput(#[from] InputParserError),
|
||||
#[error("Could not parse the configure-output action")]
|
||||
ConfigureOutput(#[from] OutputParserError),
|
||||
#[error("Could not parse the environment variables")]
|
||||
Env(#[from] EnvParserError),
|
||||
#[error("Could not parse a set-keymap action")]
|
||||
SetKeymap(#[from] KeymapParserError),
|
||||
#[error("Could not parse a set-status action")]
|
||||
Status(#[from] StatusParserError),
|
||||
#[error("Could not parse a set-theme action")]
|
||||
Theme(#[from] ThemeParserError),
|
||||
#[error("Could not parse a set-log-level action")]
|
||||
SetLogLevel(#[from] LogLevelParserError),
|
||||
#[error("Could not parse a set-gfx-api action")]
|
||||
GfxApi(#[from] GfxApiParserError),
|
||||
#[error("Could not parse a configure-drm-device action")]
|
||||
DrmDevice(#[from] DrmDeviceParserError),
|
||||
#[error("Could not parse a set-render-device action")]
|
||||
SetRenderDevice(#[from] DrmDeviceMatchParserError),
|
||||
#[error("Could not parse a configure-idle action")]
|
||||
ConfigureIdle(#[from] IdleParserError),
|
||||
}
|
||||
|
||||
pub struct ActionParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl ActionParser<'_> {
|
||||
fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
use {crate::config::SimpleCommand::*, jay_config::Direction::*};
|
||||
let cmd = match string {
|
||||
"focus-left" => Focus(Left),
|
||||
"focus-down" => Focus(Down),
|
||||
"focus-up" => Focus(Up),
|
||||
"focus-right" => Focus(Right),
|
||||
"move-left" => Move(Left),
|
||||
"move-down" => Move(Down),
|
||||
"move-up" => Move(Up),
|
||||
"move-right" => Move(Right),
|
||||
"split-horizontal" => Split(Horizontal),
|
||||
"split-vertical" => Split(Vertical),
|
||||
"toggle-split" => ToggleSplit,
|
||||
"toggle-mono" => ToggleMono,
|
||||
"toggle-fullscreen" => ToggleFullscreen,
|
||||
"focus-parent" => FocusParent,
|
||||
"close" => Close,
|
||||
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||
"toggle-floating" => ToggleFloating,
|
||||
"quit" => Quit,
|
||||
"reload-config-toml" => ReloadConfigToml,
|
||||
"reload-config-so" => ReloadConfigSo,
|
||||
"none" => None,
|
||||
_ => {
|
||||
return Err(ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span))
|
||||
}
|
||||
};
|
||||
Ok(Action::SimpleCommand { cmd })
|
||||
}
|
||||
|
||||
fn parse_multi(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut actions = vec![];
|
||||
for el in array {
|
||||
actions.push(el.parse(self)?);
|
||||
}
|
||||
Ok(Action::Multi { actions })
|
||||
}
|
||||
|
||||
fn parse_exec(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let exec = ext
|
||||
.extract(val("exec"))?
|
||||
.parse_map(&mut ExecParser(self.0))?;
|
||||
Ok(Action::Exec { exec })
|
||||
}
|
||||
|
||||
fn parse_switch_to_vt(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let num = ext.extract(n32("num"))?.value;
|
||||
Ok(Action::SwitchToVt { num })
|
||||
}
|
||||
|
||||
fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let name = ext.extract(str("name"))?.value.to_string();
|
||||
Ok(Action::ShowWorkspace { name })
|
||||
}
|
||||
|
||||
fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let name = ext.extract(str("name"))?.value.to_string();
|
||||
Ok(Action::ShowWorkspace { name })
|
||||
}
|
||||
|
||||
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let con = ext
|
||||
.extract(val("connector"))?
|
||||
.parse_map(&mut ConnectorParser(self.0))?;
|
||||
Ok(Action::ConfigureConnector { con })
|
||||
}
|
||||
|
||||
fn parse_configure_input(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let input = ext.extract(val("input"))?.parse_map(&mut InputParser {
|
||||
cx: self.0,
|
||||
tag_ok: false,
|
||||
})?;
|
||||
Ok(Action::ConfigureInput { input })
|
||||
}
|
||||
|
||||
fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let idle = ext
|
||||
.extract(val("idle"))?
|
||||
.parse_map(&mut IdleParser(self.0))?;
|
||||
Ok(Action::ConfigureIdle { idle })
|
||||
}
|
||||
|
||||
fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let out = ext.extract(val("output"))?.parse_map(&mut OutputParser {
|
||||
cx: self.0,
|
||||
name_ok: false,
|
||||
})?;
|
||||
Ok(Action::ConfigureOutput { out })
|
||||
}
|
||||
|
||||
fn parse_set_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let env = ext.extract(val("env"))?.parse_map(&mut EnvParser)?;
|
||||
Ok(Action::SetEnv { env })
|
||||
}
|
||||
|
||||
fn parse_unset_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
struct P;
|
||||
impl Parser for P {
|
||||
type Value = Vec<String>;
|
||||
type Error = ActionParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
|
||||
|
||||
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
|
||||
Ok(vec![string.to_string()])
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for v in array {
|
||||
res.push(v.parse_map(&mut StringParser)?);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
let env = ext.extract(val("env"))?.parse_map(&mut P)?;
|
||||
Ok(Action::UnsetEnv { env })
|
||||
}
|
||||
|
||||
fn parse_set_keymap(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let map = ext.extract(val("map"))?.parse_map(&mut KeymapParser {
|
||||
cx: self.0,
|
||||
definition: false,
|
||||
})?;
|
||||
Ok(Action::SetKeymap { map })
|
||||
}
|
||||
|
||||
fn parse_set_status(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let status = match ext.extract(opt(val("status")))? {
|
||||
None => None,
|
||||
Some(v) => Some(v.parse_map(&mut StatusParser(self.0))?),
|
||||
};
|
||||
Ok(Action::SetStatus { status })
|
||||
}
|
||||
|
||||
fn parse_set_theme(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let theme = ext
|
||||
.extract(val("theme"))?
|
||||
.parse_map(&mut ThemeParser(self.0))?;
|
||||
Ok(Action::SetTheme {
|
||||
theme: Box::new(theme),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_set_log_level(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let level = ext.extract(val("level"))?.parse_map(&mut LogLevelParser)?;
|
||||
Ok(Action::SetLogLevel { level })
|
||||
}
|
||||
|
||||
fn parse_set_gfx_api(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let api = ext.extract(val("api"))?.parse_map(&mut GfxApiParser)?;
|
||||
Ok(Action::SetGfxApi { api })
|
||||
}
|
||||
|
||||
fn parse_set_render_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let dev = ext
|
||||
.extract(val("dev"))?
|
||||
.parse_map(&mut DrmDeviceMatchParser(self.0))?;
|
||||
Ok(Action::SetRenderDevice { dev })
|
||||
}
|
||||
|
||||
fn parse_configure_direct_scanout(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let enabled = ext.extract(bol("enabled"))?.value;
|
||||
Ok(Action::ConfigureDirectScanout { enabled })
|
||||
}
|
||||
|
||||
fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let dev = ext.extract(val("dev"))?.parse_map(&mut DrmDeviceParser {
|
||||
cx: self.0,
|
||||
name_ok: false,
|
||||
})?;
|
||||
Ok(Action::ConfigureDrmDevice { dev })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser for ActionParser<'a> {
|
||||
type Value = Action;
|
||||
type Error = ActionParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
self.parse_simple_cmd(span, string)
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
self.parse_multi(span, array)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let ty = ext.extract_or_ignore(str("type"))?;
|
||||
let res = match ty.value {
|
||||
"simple" => {
|
||||
let cmd = ext.extract(str("cmd"))?;
|
||||
self.parse_simple_cmd(cmd.span, cmd.value)
|
||||
}
|
||||
"multi" => {
|
||||
let actions = ext.extract(arr("actions"))?;
|
||||
self.parse_multi(actions.span, actions.value)
|
||||
}
|
||||
"exec" => self.parse_exec(&mut ext),
|
||||
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
|
||||
"show-workspace" => self.parse_show_workspace(&mut ext),
|
||||
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
||||
"configure-connector" => self.parse_configure_connector(&mut ext),
|
||||
"configure-input" => self.parse_configure_input(&mut ext),
|
||||
"configure-output" => self.parse_configure_output(&mut ext),
|
||||
"set-env" => self.parse_set_env(&mut ext),
|
||||
"unset-env" => self.parse_unset_env(&mut ext),
|
||||
"set-keymap" => self.parse_set_keymap(&mut ext),
|
||||
"set-status" => self.parse_set_status(&mut ext),
|
||||
"set-theme" => self.parse_set_theme(&mut ext),
|
||||
"set-log-level" => self.parse_set_log_level(&mut ext),
|
||||
"set-gfx-api" => self.parse_set_gfx_api(&mut ext),
|
||||
"configure-direct-scanout" => self.parse_configure_direct_scanout(&mut ext),
|
||||
"configure-drm-device" => self.parse_configure_drm_device(&mut ext),
|
||||
"set-render-device" => self.parse_set_render_device(&mut ext),
|
||||
"configure-idle" => self.parse_configure_idle(&mut ext),
|
||||
v => {
|
||||
ext.ignore_unused();
|
||||
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
||||
}
|
||||
};
|
||||
drop(ext);
|
||||
res
|
||||
}
|
||||
}
|
||||
58
toml-config/src/config/parsers/color.rs
Normal file
58
toml-config/src/config/parsers/color.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::ExtractorError,
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::toml_span::{Span, SpannedExt},
|
||||
},
|
||||
jay_config::theme::Color,
|
||||
std::{num::ParseIntError, ops::Range},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ColorParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ColorParserError {
|
||||
#[error(transparent)]
|
||||
DataType(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extractor(#[from] ExtractorError),
|
||||
#[error("Color must start with `#`")]
|
||||
Prefix,
|
||||
#[error("String must have length 4, 5, 6, or 9")]
|
||||
Length,
|
||||
#[error(transparent)]
|
||||
ParseIntError(#[from] ParseIntError),
|
||||
}
|
||||
|
||||
impl Parser for ColorParser<'_> {
|
||||
type Value = Color;
|
||||
type Error = ColorParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let hex = match string.strip_prefix("#") {
|
||||
Some(s) => s,
|
||||
_ => return Err(ColorParserError::Prefix.spanned(span)),
|
||||
};
|
||||
let d = |range: Range<usize>| {
|
||||
u8::from_str_radix(&hex[range], 16)
|
||||
.map_err(|e| ColorParserError::ParseIntError(e).spanned(span))
|
||||
};
|
||||
let s = |range: Range<usize>| {
|
||||
let v = d(range)?;
|
||||
Ok((v << 4) | v)
|
||||
};
|
||||
let (r, g, b, a) = match hex.len() {
|
||||
3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX),
|
||||
4 => (s(0..1)?, s(1..2)?, s(2..3)?, s(3..4)?),
|
||||
6 => (d(0..2)?, d(2..4)?, d(4..6)?, u8::MAX),
|
||||
8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(4..8)?),
|
||||
_ => return Err(ColorParserError::Length.spanned(span)),
|
||||
};
|
||||
Ok(Color::new_straight(r, g, b, a))
|
||||
}
|
||||
}
|
||||
279
toml-config/src/config/parsers/config.rs
Normal file
279
toml-config/src/config/parsers/config.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{arr, bol, opt, recover, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
action::ActionParser,
|
||||
connector::ConnectorsParser,
|
||||
drm_device::DrmDevicesParser,
|
||||
drm_device_match::DrmDeviceMatchParser,
|
||||
env::EnvParser,
|
||||
gfx_api::GfxApiParser,
|
||||
idle::IdleParser,
|
||||
input::InputsParser,
|
||||
keymap::KeymapParser,
|
||||
log_level::LogLevelParser,
|
||||
output::OutputsParser,
|
||||
shortcuts::{ShortcutsParser, ShortcutsParserError},
|
||||
status::StatusParser,
|
||||
theme::ThemeParser,
|
||||
},
|
||||
spanned::SpannedErrorExt,
|
||||
Action, Config, Theme,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extractor(#[from] ExtractorError),
|
||||
#[error("Could not parse the shortcuts")]
|
||||
ParseShortcuts(#[source] ShortcutsParserError),
|
||||
}
|
||||
|
||||
pub struct ConfigParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl ConfigParser<'_> {
|
||||
fn parse_action(&self, name: &str, action: Option<Spanned<&Value>>) -> Option<Action> {
|
||||
match action {
|
||||
None => None,
|
||||
Some(value) => match value.parse(&mut ActionParser(self.0)) {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the {name} action: {}", self.0.error(e));
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for ConfigParser<'_> {
|
||||
type Value = Config;
|
||||
type Error = ConfigParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (
|
||||
(
|
||||
keymap_val,
|
||||
shortcuts_val,
|
||||
on_graphics_init_val,
|
||||
status_val,
|
||||
outputs_val,
|
||||
connectors_val,
|
||||
workspace_capture,
|
||||
env_val,
|
||||
on_startup_val,
|
||||
keymaps_val,
|
||||
),
|
||||
(
|
||||
log_level_val,
|
||||
theme_val,
|
||||
gfx_api_val,
|
||||
drm_devices_val,
|
||||
direct_scanout,
|
||||
render_device_val,
|
||||
inputs_val,
|
||||
on_idle_val,
|
||||
_,
|
||||
idle_val,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
opt(val("shortcuts")),
|
||||
opt(val("on-graphics-initialized")),
|
||||
opt(val("status")),
|
||||
opt(val("outputs")),
|
||||
opt(val("connectors")),
|
||||
recover(opt(bol("workspace-capture"))),
|
||||
opt(val("env")),
|
||||
opt(val("on-startup")),
|
||||
recover(opt(arr("keymaps"))),
|
||||
),
|
||||
(
|
||||
opt(val("log-level")),
|
||||
opt(val("theme")),
|
||||
opt(val("gfx-api")),
|
||||
opt(val("drm-devices")),
|
||||
recover(opt(bol("direct-scanout"))),
|
||||
opt(val("render-device")),
|
||||
opt(val("inputs")),
|
||||
opt(val("on-idle")),
|
||||
opt(val("$schema")),
|
||||
opt(val("idle")),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
if let Some(value) = keymap_val {
|
||||
match value.parse(&mut KeymapParser {
|
||||
cx: self.0,
|
||||
definition: false,
|
||||
}) {
|
||||
Ok(m) => keymap = Some(m),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the keymap: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut shortcuts = vec![];
|
||||
if let Some(value) = shortcuts_val {
|
||||
shortcuts = value
|
||||
.parse(&mut ShortcutsParser(self.0))
|
||||
.map_spanned_err(ConfigParserError::ParseShortcuts)?;
|
||||
}
|
||||
if shortcuts.is_empty() {
|
||||
log::warn!("Config defines no shortcuts");
|
||||
}
|
||||
let on_graphics_initialized =
|
||||
self.parse_action("on-graphics-initialized", on_graphics_init_val);
|
||||
let on_idle = self.parse_action("on-idle", on_idle_val);
|
||||
let on_startup = self.parse_action("on-startup", on_startup_val);
|
||||
let mut status = None;
|
||||
if let Some(value) = status_val {
|
||||
match value.parse(&mut StatusParser(self.0)) {
|
||||
Ok(v) => status = Some(v),
|
||||
Err(e) => log::warn!("Could not parse the status config: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
let mut outputs = vec![];
|
||||
if let Some(value) = outputs_val {
|
||||
match value.parse(&mut OutputsParser(self.0)) {
|
||||
Ok(v) => outputs = v,
|
||||
Err(e) => log::warn!("Could not parse the outputs: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
let mut connectors = vec![];
|
||||
if let Some(value) = connectors_val {
|
||||
match value.parse(&mut ConnectorsParser(self.0)) {
|
||||
Ok(v) => connectors = v,
|
||||
Err(e) => log::warn!("Could not parse the connectors: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
let mut env = vec![];
|
||||
if let Some(value) = env_val {
|
||||
match value.parse(&mut EnvParser) {
|
||||
Ok(v) => env = v,
|
||||
Err(e) => log::warn!(
|
||||
"Could not parse the environment variables: {}",
|
||||
self.0.error(e)
|
||||
),
|
||||
}
|
||||
}
|
||||
let mut keymaps = vec![];
|
||||
if let Some(value) = keymaps_val {
|
||||
for value in value.value {
|
||||
match value.parse(&mut KeymapParser {
|
||||
cx: self.0,
|
||||
definition: true,
|
||||
}) {
|
||||
Ok(m) => keymaps.push(m),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse a keymap: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut log_level = None;
|
||||
if let Some(value) = log_level_val {
|
||||
match value.parse(&mut LogLevelParser) {
|
||||
Ok(v) => log_level = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the log level: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut theme = Theme::default();
|
||||
if let Some(value) = theme_val {
|
||||
match value.parse(&mut ThemeParser(self.0)) {
|
||||
Ok(v) => theme = v,
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the theme: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut gfx_api = None;
|
||||
if let Some(value) = gfx_api_val {
|
||||
match value.parse(&mut GfxApiParser) {
|
||||
Ok(v) => gfx_api = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the graphics API: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut drm_devices = vec![];
|
||||
if let Some(value) = drm_devices_val {
|
||||
match value.parse(&mut DrmDevicesParser(self.0)) {
|
||||
Ok(v) => drm_devices = v,
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the drm devices: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut render_device = None;
|
||||
if let Some(value) = render_device_val {
|
||||
match value.parse(&mut DrmDeviceMatchParser(self.0)) {
|
||||
Ok(v) => render_device = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the render device: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut inputs = vec![];
|
||||
if let Some(value) = inputs_val {
|
||||
match value.parse(&mut InputsParser(self.0)) {
|
||||
Ok(v) => inputs = v,
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the inputs: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut idle = None;
|
||||
if let Some(value) = idle_val {
|
||||
match value.parse(&mut IdleParser(self.0)) {
|
||||
Ok(v) => idle = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the idle timeout: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
shortcuts,
|
||||
on_graphics_initialized,
|
||||
on_idle,
|
||||
status,
|
||||
outputs,
|
||||
connectors,
|
||||
workspace_capture: workspace_capture.despan().unwrap_or(true),
|
||||
env,
|
||||
on_startup,
|
||||
keymaps,
|
||||
log_level,
|
||||
theme,
|
||||
gfx_api,
|
||||
drm_devices,
|
||||
direct_scanout_enabled: direct_scanout.despan(),
|
||||
render_device,
|
||||
inputs,
|
||||
idle,
|
||||
})
|
||||
}
|
||||
}
|
||||
83
toml-config/src/config/parsers/connector.rs
Normal file
83
toml-config/src/config/parsers/connector.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{bol, opt, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::connector_match::{ConnectorMatchParser, ConnectorMatchParserError},
|
||||
ConfigConnector,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConnectorParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Match(#[from] ConnectorMatchParserError),
|
||||
}
|
||||
|
||||
pub struct ConnectorParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for ConnectorParser<'a> {
|
||||
type Value = ConfigConnector;
|
||||
type Error = ConnectorParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (match_val, enabled) = ext.extract((val("match"), opt(bol("enabled"))))?;
|
||||
Ok(ConfigConnector {
|
||||
match_: match_val.parse_map(&mut ConnectorMatchParser(self.0))?,
|
||||
enabled: enabled.despan().unwrap_or(true),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectorsParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for ConnectorsParser<'a> {
|
||||
type Value = Vec<ConfigConnector>;
|
||||
type Error = ConnectorParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(&mut ConnectorParser(self.0)) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse connector: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
log::warn!(
|
||||
"`connectors` value should be an array: {}",
|
||||
self.0.error3(span)
|
||||
);
|
||||
ConnectorParser(self.0)
|
||||
.parse_table(span, table)
|
||||
.map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
57
toml-config/src/config/parsers/connector_match.rs
Normal file
57
toml-config/src/config/parsers/connector_match.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{opt, str, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
ConnectorMatch,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConnectorMatchParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct ConnectorMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for ConnectorMatchParser<'a> {
|
||||
type Value = ConnectorMatch;
|
||||
type Error = ConnectorMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(self) {
|
||||
Ok(m) => res.push(m),
|
||||
Err(e) => {
|
||||
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ConnectorMatch::Any(res))
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (connector,) = ext.extract((opt(str("name")),))?;
|
||||
Ok(ConnectorMatch::All {
|
||||
connector: connector.map(|v| v.value.to_owned()),
|
||||
})
|
||||
}
|
||||
}
|
||||
126
toml-config/src/config/parsers/drm_device.rs
Normal file
126
toml-config/src/config/parsers/drm_device.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{bol, opt, recover, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
|
||||
gfx_api::GfxApiParser,
|
||||
},
|
||||
ConfigDrmDevice,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DrmDeviceParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Match(#[from] DrmDeviceMatchParserError),
|
||||
}
|
||||
|
||||
pub struct DrmDeviceParser<'a> {
|
||||
pub cx: &'a Context<'a>,
|
||||
pub name_ok: bool,
|
||||
}
|
||||
|
||||
impl<'a> Parser for DrmDeviceParser<'a> {
|
||||
type Value = ConfigDrmDevice;
|
||||
type Error = DrmDeviceParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.cx, span, table);
|
||||
let (name, match_val, direct_scanout_enabled, gfx_api_val) = ext.extract((
|
||||
opt(str("name")),
|
||||
val("match"),
|
||||
recover(opt(bol("direct-scanout"))),
|
||||
opt(val("gfx-api")),
|
||||
))?;
|
||||
let gfx_api = match gfx_api_val {
|
||||
Some(api) => match api.parse(&mut GfxApiParser) {
|
||||
Ok(m) => Some(m),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse graphics API: {}", self.cx.error(e));
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
if let Some(name) = name {
|
||||
if self.name_ok {
|
||||
self.cx
|
||||
.used
|
||||
.borrow_mut()
|
||||
.defined_drm_devices
|
||||
.insert(name.into());
|
||||
} else {
|
||||
log::warn!(
|
||||
"DRM device names have no effect in this position (did you mean match.name?): {}",
|
||||
self.cx.error3(name.span)
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(ConfigDrmDevice {
|
||||
name: name.despan().map(|v| v.to_string()),
|
||||
match_: match_val.parse_map(&mut DrmDeviceMatchParser(self.cx))?,
|
||||
direct_scanout_enabled: direct_scanout_enabled.despan(),
|
||||
gfx_api,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrmDevicesParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for DrmDevicesParser<'a> {
|
||||
type Value = Vec<ConfigDrmDevice>;
|
||||
type Error = DrmDeviceParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(&mut DrmDeviceParser {
|
||||
cx: self.0,
|
||||
name_ok: true,
|
||||
}) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse drm device: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
log::warn!(
|
||||
"`drm-devices` value should be an array: {}",
|
||||
self.0.error3(span)
|
||||
);
|
||||
DrmDeviceParser {
|
||||
cx: self.0,
|
||||
name_ok: true,
|
||||
}
|
||||
.parse_table(span, table)
|
||||
.map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
74
toml-config/src/config/parsers/drm_device_match.rs
Normal file
74
toml-config/src/config/parsers/drm_device_match.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{n32, opt, recover, str, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
DrmDeviceMatch,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DrmDeviceMatchParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct DrmDeviceMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for DrmDeviceMatchParser<'_> {
|
||||
type Value = DrmDeviceMatch;
|
||||
type Error = DrmDeviceMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(self) {
|
||||
Ok(m) => res.push(m),
|
||||
Err(e) => {
|
||||
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(DrmDeviceMatch::Any(res))
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (name, syspath, vendor, vendor_name, model, model_name, devnode) = ext.extract((
|
||||
recover(opt(str("name"))),
|
||||
recover(opt(str("syspath"))),
|
||||
recover(opt(n32("pci-vendor"))),
|
||||
recover(opt(str("vendor"))),
|
||||
recover(opt(n32("pci-model"))),
|
||||
recover(opt(str("model"))),
|
||||
recover(opt(str("devnode"))),
|
||||
))?;
|
||||
if let Some(name) = name {
|
||||
self.0.used.borrow_mut().drm_devices.push(name.into());
|
||||
}
|
||||
Ok(DrmDeviceMatch::All {
|
||||
name: name.despan_into(),
|
||||
syspath: syspath.despan_into(),
|
||||
vendor: vendor.despan(),
|
||||
vendor_name: vendor_name.despan_into(),
|
||||
model: model.despan(),
|
||||
model_name: model_name.despan_into(),
|
||||
devnode: devnode.despan_into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
42
toml-config/src/config/parsers/env.rs
Normal file
42
toml-config/src/config/parsers/env.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{StringParser, StringParserError},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnvParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
String(#[from] StringParserError),
|
||||
}
|
||||
|
||||
pub struct EnvParser;
|
||||
|
||||
impl Parser for EnvParser {
|
||||
type Value = Vec<(String, String)>;
|
||||
type Error = EnvParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
_span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut envs = vec![];
|
||||
for (k, v) in table {
|
||||
envs.push((k.value.to_string(), v.parse_map(&mut StringParser)?));
|
||||
}
|
||||
Ok(envs)
|
||||
}
|
||||
}
|
||||
91
toml-config/src/config/parsers/exec.rs
Normal file
91
toml-config/src/config/parsers/exec.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{arr, opt, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
env::{EnvParser, EnvParserError},
|
||||
StringParser, StringParserError,
|
||||
},
|
||||
Exec,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExecParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extractor(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
String(#[from] StringParserError),
|
||||
#[error(transparent)]
|
||||
Env(#[from] EnvParserError),
|
||||
#[error("Array cannot be empty")]
|
||||
Empty,
|
||||
}
|
||||
|
||||
pub struct ExecParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ExecParser<'_> {
|
||||
type Value = Exec;
|
||||
type Error = ExecParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
|
||||
|
||||
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
|
||||
Ok(Exec {
|
||||
prog: string.to_string(),
|
||||
args: vec![],
|
||||
envs: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
if array.is_empty() {
|
||||
return Err(ExecParserError::Empty.spanned(span));
|
||||
}
|
||||
let prog = array[0].parse_map(&mut StringParser)?;
|
||||
let mut args = vec![];
|
||||
for v in &array[1..] {
|
||||
args.push(v.parse_map(&mut StringParser)?);
|
||||
}
|
||||
Ok(Exec {
|
||||
prog,
|
||||
args,
|
||||
envs: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (prog, args_val, envs_val) =
|
||||
ext.extract((str("prog"), opt(arr("args")), opt(val("env"))))?;
|
||||
let mut args = vec![];
|
||||
if let Some(args_val) = args_val {
|
||||
for arg in args_val.value {
|
||||
args.push(arg.parse_map(&mut StringParser)?);
|
||||
}
|
||||
}
|
||||
let envs = match envs_val {
|
||||
None => vec![],
|
||||
Some(e) => e.parse_map(&mut EnvParser)?,
|
||||
};
|
||||
Ok(Exec {
|
||||
prog: prog.value.to_string(),
|
||||
args,
|
||||
envs,
|
||||
})
|
||||
}
|
||||
}
|
||||
34
toml-config/src/config/parsers/gfx_api.rs
Normal file
34
toml-config/src/config/parsers/gfx_api.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use {
|
||||
crate::{
|
||||
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
toml::toml_span::{Span, SpannedExt},
|
||||
},
|
||||
jay_config::video::GfxApi,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct GfxApiParser;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GfxApiParserError {
|
||||
#[error(transparent)]
|
||||
DataType(#[from] UnexpectedDataType),
|
||||
#[error("Unknown API {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl Parser for GfxApiParser {
|
||||
type Value = GfxApi;
|
||||
type Error = GfxApiParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
use GfxApi::*;
|
||||
let api = match string.to_ascii_lowercase().as_str() {
|
||||
"opengl" => OpenGl,
|
||||
"vulkan" => Vulkan,
|
||||
_ => return Err(GfxApiParserError::Unknown(string.to_string()).spanned(span)),
|
||||
};
|
||||
Ok(api)
|
||||
}
|
||||
}
|
||||
45
toml-config/src/config/parsers/idle.rs
Normal file
45
toml-config/src/config/parsers/idle.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{n64, opt, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
std::time::Duration,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IdleParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct IdleParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for IdleParser<'_> {
|
||||
type Value = Duration;
|
||||
type Error = IdleParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (minutes, seconds) = ext.extract((opt(n64("minutes")), opt(n64("seconds"))))?;
|
||||
let idle = Duration::from_secs(
|
||||
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
|
||||
);
|
||||
Ok(idle)
|
||||
}
|
||||
}
|
||||
205
toml-config/src/config/parsers/input.rs
Normal file
205
toml-config/src/config/parsers/input.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{bol, fltorint, opt, recover, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::input_match::{InputMatchParser, InputMatchParserError},
|
||||
Input,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::input::acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum InputParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Match(#[from] InputMatchParserError),
|
||||
#[error("Transform matrix must have exactly two rows")]
|
||||
TwoRows,
|
||||
#[error("Transform matrix must have exactly two columns")]
|
||||
TwoColumns,
|
||||
#[error("Transform matrix entries must be floats")]
|
||||
Float,
|
||||
}
|
||||
|
||||
pub struct InputParser<'a> {
|
||||
pub cx: &'a Context<'a>,
|
||||
pub tag_ok: bool,
|
||||
}
|
||||
|
||||
impl<'a> Parser for InputParser<'a> {
|
||||
type Value = Input;
|
||||
type Error = InputParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.cx, span, table);
|
||||
let (
|
||||
(
|
||||
tag,
|
||||
match_val,
|
||||
accel_profile,
|
||||
accel_speed,
|
||||
tap_enabled,
|
||||
tap_drag_enabled,
|
||||
tap_drag_lock_enabled,
|
||||
left_handed,
|
||||
natural_scrolling,
|
||||
px_per_wheel_scroll,
|
||||
),
|
||||
(transform_matrix,),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(str("tag")),
|
||||
val("match"),
|
||||
recover(opt(str("accel-profile"))),
|
||||
recover(opt(fltorint("accel-speed"))),
|
||||
recover(opt(bol("tap-enabled"))),
|
||||
recover(opt(bol("tap-drag-enabled"))),
|
||||
recover(opt(bol("tap-drag-lock-enabled"))),
|
||||
recover(opt(bol("left-handed"))),
|
||||
recover(opt(bol("natural-scrolling"))),
|
||||
recover(opt(fltorint("px-per-wheel-scroll"))),
|
||||
),
|
||||
(recover(opt(val("transform-matrix"))),),
|
||||
))?;
|
||||
let accel_profile = match accel_profile {
|
||||
None => None,
|
||||
Some(p) => match p.value.to_ascii_lowercase().as_str() {
|
||||
"flat" => Some(ACCEL_PROFILE_FLAT),
|
||||
"adaptive" => Some(ACCEL_PROFILE_ADAPTIVE),
|
||||
v => {
|
||||
log::warn!("Unknown accel-profile {v}: {}", self.cx.error3(p.span));
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
let transform_matrix = match transform_matrix {
|
||||
None => None,
|
||||
Some(matrix) => match matrix.parse(&mut TransformMatrixParser) {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse transform matrix: {}", self.cx.error(e));
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
if let Some(tag) = tag {
|
||||
if self.tag_ok {
|
||||
self.cx.used.borrow_mut().defined_inputs.insert(tag.into());
|
||||
} else {
|
||||
log::warn!(
|
||||
"Input tags have no effect in this position (did you mean match.tag?): {}",
|
||||
self.cx.error3(tag.span)
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Input {
|
||||
tag: tag.despan_into(),
|
||||
match_: match_val.parse_map(&mut InputMatchParser(self.cx))?,
|
||||
accel_profile,
|
||||
accel_speed: accel_speed.despan(),
|
||||
tap_enabled: tap_enabled.despan(),
|
||||
tap_drag_enabled: tap_drag_enabled.despan(),
|
||||
tap_drag_lock_enabled: tap_drag_lock_enabled.despan(),
|
||||
left_handed: left_handed.despan(),
|
||||
natural_scrolling: natural_scrolling.despan(),
|
||||
px_per_wheel_scroll: px_per_wheel_scroll.despan(),
|
||||
transform_matrix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputsParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for InputsParser<'a> {
|
||||
type Value = Vec<Input>;
|
||||
type Error = InputParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(&mut InputParser {
|
||||
cx: self.0,
|
||||
tag_ok: true,
|
||||
}) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse output: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
log::warn!(
|
||||
"`outputs` value should be an array: {}",
|
||||
self.0.error3(span)
|
||||
);
|
||||
InputParser {
|
||||
cx: self.0,
|
||||
tag_ok: true,
|
||||
}
|
||||
.parse_table(span, table)
|
||||
.map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
|
||||
struct TransformMatrixParser;
|
||||
|
||||
impl Parser for TransformMatrixParser {
|
||||
type Value = [[f64; 2]; 2];
|
||||
type Error = InputParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
if array.len() != 2 {
|
||||
return Err(InputParserError::TwoRows.spanned(span));
|
||||
}
|
||||
Ok([
|
||||
array[0].parse(&mut TransformMatrixRowParser)?,
|
||||
array[1].parse(&mut TransformMatrixRowParser)?,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
struct TransformMatrixRowParser;
|
||||
|
||||
impl Parser for TransformMatrixRowParser {
|
||||
type Value = [f64; 2];
|
||||
type Error = InputParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
if array.len() != 2 {
|
||||
return Err(InputParserError::TwoColumns.spanned(span));
|
||||
}
|
||||
let extract = |v: &Spanned<Value>| match v.value {
|
||||
Value::Float(f) => Ok(f),
|
||||
Value::Integer(f) => Ok(f as _),
|
||||
_ => Err(InputParserError::Float.spanned(v.span)),
|
||||
};
|
||||
Ok([extract(&array[0])?, extract(&array[1])?])
|
||||
}
|
||||
}
|
||||
98
toml-config/src/config/parsers/input_match.rs
Normal file
98
toml-config/src/config/parsers/input_match.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{bol, opt, str, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
InputMatch,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum InputMatchParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct InputMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for InputMatchParser<'a> {
|
||||
type Value = InputMatch;
|
||||
type Error = InputMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(self) {
|
||||
Ok(m) => res.push(m),
|
||||
Err(e) => {
|
||||
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(InputMatch::Any(res))
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (
|
||||
(
|
||||
tag,
|
||||
name,
|
||||
syspath,
|
||||
devnode,
|
||||
is_keyboard,
|
||||
is_pointer,
|
||||
is_touch,
|
||||
is_tablet_tool,
|
||||
is_tablet_pad,
|
||||
is_gesture,
|
||||
),
|
||||
(is_switch,),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(str("tag")),
|
||||
opt(str("name")),
|
||||
opt(str("syspath")),
|
||||
opt(str("devnode")),
|
||||
opt(bol("is-keyboard")),
|
||||
opt(bol("is-pointer")),
|
||||
opt(bol("is-touch")),
|
||||
opt(bol("is-tablet-tool")),
|
||||
opt(bol("is-tablet-pad")),
|
||||
opt(bol("is-gesture")),
|
||||
),
|
||||
(opt(bol("is-switch")),),
|
||||
))?;
|
||||
if let Some(tag) = tag {
|
||||
self.0.used.borrow_mut().inputs.push(tag.into());
|
||||
}
|
||||
Ok(InputMatch::All {
|
||||
tag: tag.despan_into(),
|
||||
name: name.despan_into(),
|
||||
syspath: syspath.despan_into(),
|
||||
devnode: devnode.despan_into(),
|
||||
is_keyboard: is_keyboard.despan(),
|
||||
is_pointer: is_pointer.despan(),
|
||||
is_touch: is_touch.despan(),
|
||||
is_tablet_tool: is_tablet_tool.despan(),
|
||||
is_tablet_pad: is_tablet_pad.despan(),
|
||||
is_gesture: is_gesture.despan(),
|
||||
is_switch: is_switch.despan(),
|
||||
})
|
||||
}
|
||||
}
|
||||
123
toml-config/src/config/parsers/keymap.rs
Normal file
123
toml-config/src/config/parsers/keymap.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{opt, str, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
ConfigKeymap,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::{
|
||||
config_dir,
|
||||
keyboard::{parse_keymap, Keymap},
|
||||
},
|
||||
std::{io, path::PathBuf},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum KeymapParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extractor(#[from] ExtractorError),
|
||||
#[error("The keymap is invalid")]
|
||||
Invalid,
|
||||
#[error("Keymap table must contain at least one of `name`, `map`")]
|
||||
MissingField,
|
||||
#[error("Keymap must have both `name` and `map` fields in this context")]
|
||||
DefinitionRequired,
|
||||
#[error("Could not read {0}")]
|
||||
ReadFile(String, #[source] io::Error),
|
||||
}
|
||||
|
||||
pub struct KeymapParser<'a> {
|
||||
pub cx: &'a Context<'a>,
|
||||
pub definition: bool,
|
||||
}
|
||||
|
||||
impl Parser for KeymapParser<'_> {
|
||||
type Value = ConfigKeymap;
|
||||
type Error = KeymapParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Table];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
Ok(ConfigKeymap::Literal(parse(span, string)?))
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.cx, span, table);
|
||||
let (mut name_val, mut map_val, mut path) =
|
||||
ext.extract((opt(str("name")), opt(str("map")), opt(str("path"))))?;
|
||||
if map_val.is_some() && path.is_some() {
|
||||
log::warn!(
|
||||
"Both `name` and `path` are specified. Ignoring `path`: {}",
|
||||
self.cx.error3(span)
|
||||
);
|
||||
path = None;
|
||||
}
|
||||
let file_content;
|
||||
if let Some(path) = path {
|
||||
let mut root = PathBuf::from(config_dir());
|
||||
root.push(path.value);
|
||||
file_content = match std::fs::read_to_string(&root) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return Err(KeymapParserError::ReadFile(root.display().to_string(), e)
|
||||
.spanned(path.span))
|
||||
}
|
||||
};
|
||||
map_val = Some(file_content.as_str().spanned(path.span));
|
||||
}
|
||||
if self.definition && (name_val.is_none() || map_val.is_none()) {
|
||||
return Err(KeymapParserError::DefinitionRequired.spanned(span));
|
||||
}
|
||||
if !self.definition && map_val.is_some() {
|
||||
if let Some(val) = name_val {
|
||||
log::warn!(
|
||||
"Cannot use both `name` and `map` in this position. Ignoring `name`: {}",
|
||||
self.cx.error3(val.span)
|
||||
);
|
||||
}
|
||||
name_val = None;
|
||||
}
|
||||
if let Some(name) = name_val {
|
||||
if self.definition {
|
||||
self.cx
|
||||
.used
|
||||
.borrow_mut()
|
||||
.defined_keymaps
|
||||
.insert(name.into());
|
||||
} else {
|
||||
self.cx.used.borrow_mut().keymaps.push(name.into());
|
||||
}
|
||||
}
|
||||
let res = match (name_val, map_val) {
|
||||
(Some(name_val), Some(map_val)) => ConfigKeymap::Defined {
|
||||
name: name_val.value.to_string(),
|
||||
map: parse(map_val.span, map_val.value)?,
|
||||
},
|
||||
(Some(name_val), None) => ConfigKeymap::Named(name_val.value.to_string()),
|
||||
(None, Some(map_val)) => ConfigKeymap::Literal(parse(map_val.span, map_val.value)?),
|
||||
(None, None) => return Err(KeymapParserError::MissingField.spanned(span)),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(span: Span, string: &str) -> Result<Keymap, Spanned<KeymapParserError>> {
|
||||
let map = parse_keymap(string);
|
||||
match map.is_valid() {
|
||||
true => Ok(map),
|
||||
false => Err(KeymapParserError::Invalid.spanned(span)),
|
||||
}
|
||||
}
|
||||
37
toml-config/src/config/parsers/log_level.rs
Normal file
37
toml-config/src/config/parsers/log_level.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use {
|
||||
crate::{
|
||||
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
toml::toml_span::{Span, SpannedExt},
|
||||
},
|
||||
jay_config::logging::LogLevel,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct LogLevelParser;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LogLevelParserError {
|
||||
#[error(transparent)]
|
||||
DataType(#[from] UnexpectedDataType),
|
||||
#[error("Unknown log level {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl Parser for LogLevelParser {
|
||||
type Value = LogLevel;
|
||||
type Error = LogLevelParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
use LogLevel::*;
|
||||
let level = match string.to_ascii_lowercase().as_str() {
|
||||
"error" => Error,
|
||||
"warn" | "warning" => Warn,
|
||||
"info" => Info,
|
||||
"debug" => Debug,
|
||||
"trace" => Trace,
|
||||
_ => return Err(LogLevelParserError::Unknown(string.to_string()).spanned(span)),
|
||||
};
|
||||
Ok(level)
|
||||
}
|
||||
}
|
||||
47
toml-config/src/config/parsers/mode.rs
Normal file
47
toml-config/src/config/parsers/mode.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{flt, opt, s32, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
Mode,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ModeParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct ModeParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for ModeParser<'a> {
|
||||
type Value = Mode;
|
||||
type Error = ModeParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (width, height, refresh_rate) =
|
||||
ext.extract((s32("width"), s32("height"), opt(flt("refresh-rate"))))?;
|
||||
Ok(Mode {
|
||||
width: width.value,
|
||||
height: height.value,
|
||||
refresh_rate: refresh_rate.despan(),
|
||||
})
|
||||
}
|
||||
}
|
||||
71
toml-config/src/config/parsers/modified_keysym.rs
Normal file
71
toml-config/src/config/parsers/modified_keysym.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
keysyms::KEYSYMS,
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::toml_span::{Span, SpannedExt},
|
||||
},
|
||||
jay_config::keyboard::{
|
||||
mods::{Modifiers, ALT, CAPS, CTRL, LOCK, LOGO, MOD1, MOD2, MOD3, MOD4, MOD5, NUM, SHIFT},
|
||||
ModifiedKeySym,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ModifiedKeysymParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error("You cannot use more than one non-modifier key")]
|
||||
MoreThanOneSym,
|
||||
#[error("You must specify exactly one non-modifier key")]
|
||||
MissingSym,
|
||||
#[error("Unknown keysym {0}")]
|
||||
UnknownKeysym(String),
|
||||
}
|
||||
|
||||
pub struct ModifiedKeysymParser;
|
||||
|
||||
impl Parser for ModifiedKeysymParser {
|
||||
type Value = ModifiedKeySym;
|
||||
type Error = ModifiedKeysymParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let mut modifiers = Modifiers(0);
|
||||
let mut sym = None;
|
||||
for part in string.split("-") {
|
||||
let modifier = match part {
|
||||
"shift" => SHIFT,
|
||||
"lock" => LOCK,
|
||||
"ctrl" => CTRL,
|
||||
"mod1" => MOD1,
|
||||
"mod2" => MOD2,
|
||||
"mod3" => MOD3,
|
||||
"mod4" => MOD4,
|
||||
"mod5" => MOD5,
|
||||
"caps" => CAPS,
|
||||
"alt" => ALT,
|
||||
"num" => NUM,
|
||||
"logo" => LOGO,
|
||||
_ => match KEYSYMS.get(part) {
|
||||
Some(new) if sym.is_none() => {
|
||||
sym = Some(*new);
|
||||
continue;
|
||||
}
|
||||
Some(_) => return Err(ModifiedKeysymParserError::MoreThanOneSym.spanned(span)),
|
||||
_ => {
|
||||
return Err(ModifiedKeysymParserError::UnknownKeysym(part.to_string())
|
||||
.spanned(span))
|
||||
}
|
||||
},
|
||||
};
|
||||
modifiers |= modifier;
|
||||
}
|
||||
match sym {
|
||||
Some(s) => Ok(modifiers | s),
|
||||
None => Err(ModifiedKeysymParserError::MissingSym.spanned(span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
150
toml-config/src/config/parsers/output.rs
Normal file
150
toml-config/src/config/parsers/output.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{fltorint, opt, recover, s32, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
mode::ModeParser,
|
||||
output_match::{OutputMatchParser, OutputMatchParserError},
|
||||
},
|
||||
Output,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::video::Transform,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum OutputParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Match(#[from] OutputMatchParserError),
|
||||
}
|
||||
|
||||
pub struct OutputParser<'a> {
|
||||
pub cx: &'a Context<'a>,
|
||||
pub name_ok: bool,
|
||||
}
|
||||
|
||||
impl<'a> Parser for OutputParser<'a> {
|
||||
type Value = Output;
|
||||
type Error = OutputParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.cx, span, table);
|
||||
let (name, match_val, x, y, scale, transform, mode) = ext.extract((
|
||||
opt(str("name")),
|
||||
val("match"),
|
||||
recover(opt(s32("x"))),
|
||||
recover(opt(s32("y"))),
|
||||
recover(opt(fltorint("scale"))),
|
||||
recover(opt(str("transform"))),
|
||||
opt(val("mode")),
|
||||
))?;
|
||||
let transform = match transform {
|
||||
None => None,
|
||||
Some(t) => match t.value {
|
||||
"none" => Some(Transform::None),
|
||||
"rotate-90" => Some(Transform::Rotate90),
|
||||
"rotate-180" => Some(Transform::Rotate180),
|
||||
"rotate-270" => Some(Transform::Rotate270),
|
||||
"flip" => Some(Transform::Flip),
|
||||
"flip-rotate-90" => Some(Transform::FlipRotate90),
|
||||
"flip-rotate-180" => Some(Transform::FlipRotate180),
|
||||
"flip-rotate-270" => Some(Transform::FlipRotate270),
|
||||
_ => {
|
||||
log::warn!("Unknown transform {}: {}", t.value, self.cx.error3(t.span));
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
let mode = match mode {
|
||||
Some(mode) => match mode.parse(&mut ModeParser(self.cx)) {
|
||||
Ok(m) => Some(m),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse mode: {}", self.cx.error(e));
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
if let Some(name) = name {
|
||||
if self.name_ok {
|
||||
self.cx
|
||||
.used
|
||||
.borrow_mut()
|
||||
.defined_outputs
|
||||
.insert(name.into());
|
||||
} else {
|
||||
log::warn!(
|
||||
"Output names have no effect in this position (did you mean match.name?): {}",
|
||||
self.cx.error3(name.span)
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Output {
|
||||
name: name.despan().map(|v| v.to_string()),
|
||||
match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?,
|
||||
x: x.despan(),
|
||||
y: y.despan(),
|
||||
scale: scale.despan(),
|
||||
transform,
|
||||
mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputsParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for OutputsParser<'a> {
|
||||
type Value = Vec<Output>;
|
||||
type Error = OutputParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(&mut OutputParser {
|
||||
cx: self.0,
|
||||
name_ok: true,
|
||||
}) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse output: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
log::warn!(
|
||||
"`outputs` value should be an array: {}",
|
||||
self.0.error3(span)
|
||||
);
|
||||
OutputParser {
|
||||
cx: self.0,
|
||||
name_ok: true,
|
||||
}
|
||||
.parse_table(span, table)
|
||||
.map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
70
toml-config/src/config/parsers/output_match.rs
Normal file
70
toml-config/src/config/parsers/output_match.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{opt, str, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
OutputMatch,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum OutputMatchParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct OutputMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl<'a> Parser for OutputMatchParser<'a> {
|
||||
type Value = OutputMatch;
|
||||
type Error = OutputMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(self) {
|
||||
Ok(m) => res.push(m),
|
||||
Err(e) => {
|
||||
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(OutputMatch::Any(res))
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (name, connector, serial_number, manufacturer, model) = ext.extract((
|
||||
opt(str("name")),
|
||||
opt(str("connector")),
|
||||
opt(str("serial-number")),
|
||||
opt(str("manufacturer")),
|
||||
opt(str("model")),
|
||||
))?;
|
||||
if let Some(name) = name {
|
||||
self.0.used.borrow_mut().outputs.push(name.into());
|
||||
}
|
||||
Ok(OutputMatch::All {
|
||||
name: name.despan_into(),
|
||||
connector: connector.despan_into(),
|
||||
serial_number: serial_number.despan_into(),
|
||||
manufacturer: manufacturer.despan_into(),
|
||||
model: model.despan_into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
72
toml-config/src/config/parsers/shortcuts.rs
Normal file
72
toml-config/src/config/parsers/shortcuts.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{action::ActionParser, modified_keysym::ModifiedKeysymParser},
|
||||
Action,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::keyboard::ModifiedKeySym,
|
||||
std::collections::HashSet,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ShortcutsParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
}
|
||||
|
||||
pub struct ShortcutsParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ShortcutsParser<'_> {
|
||||
type Value = Vec<(ModifiedKeySym, Action)>;
|
||||
type Error = ShortcutsParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
_span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut used_keys = HashSet::<Spanned<ModifiedKeySym>>::new();
|
||||
let mut res = vec![];
|
||||
for (key, value) in table.iter() {
|
||||
let keysym = match ModifiedKeysymParser.parse_string(key.span, &key.value) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse keysym: {}", self.0.error(e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let action = match value.parse(&mut ActionParser(self.0)) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Could not parse action for keysym {}: {}",
|
||||
key.value,
|
||||
self.0.error(e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let spanned = keysym.spanned(key.span);
|
||||
if let Some(prev) = used_keys.get(&spanned) {
|
||||
log::warn!(
|
||||
"Duplicate key overrides previous definition: {}",
|
||||
self.0.error3(spanned.span)
|
||||
);
|
||||
log::info!("Previous definition here: {}", self.0.error3(prev.span));
|
||||
}
|
||||
used_keys.insert(spanned);
|
||||
res.push((keysym, action));
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
81
toml-config/src/config/parsers/status.rs
Normal file
81
toml-config/src/config/parsers/status.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{opt, recover, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::exec::{ExecParser, ExecParserError},
|
||||
Status,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::status::MessageFormat,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StatusParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Exec(#[from] ExecParserError),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error("Expected `plain`, `pango`, or `i3bar` but found {0}")]
|
||||
UnknownFormat(String),
|
||||
}
|
||||
|
||||
pub struct StatusParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for StatusParser<'_> {
|
||||
type Value = Status;
|
||||
type Error = StatusParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (format, exec, separator) = ext.extract((
|
||||
opt(str("format")),
|
||||
val("exec"),
|
||||
recover(opt(str("i3bar-separator"))),
|
||||
))?;
|
||||
let format = match format {
|
||||
Some(f) => match f.value {
|
||||
"plain" => MessageFormat::Plain,
|
||||
"pango" => MessageFormat::Pango,
|
||||
"i3bar" => MessageFormat::I3Bar,
|
||||
_ => {
|
||||
return Err(
|
||||
StatusParserError::UnknownFormat(f.value.to_string()).spanned(f.span)
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => MessageFormat::Plain,
|
||||
};
|
||||
let exec = exec.parse_map(&mut ExecParser(self.0))?;
|
||||
let separator = match separator {
|
||||
None => None,
|
||||
Some(sep) if format == MessageFormat::I3Bar => Some(sep.value.to_string()),
|
||||
Some(sep) => {
|
||||
log::warn!(
|
||||
"Separator has no effect for format {format:?}: {}",
|
||||
self.0.error3(sep.span)
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(Status {
|
||||
format,
|
||||
exec,
|
||||
separator,
|
||||
})
|
||||
}
|
||||
}
|
||||
119
toml-config/src/config/parsers/theme.rs
Normal file
119
toml-config/src/config/parsers/theme.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{opt, recover, s32, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::color::ColorParser,
|
||||
Theme,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ThemeParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ThemeParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extractor(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
impl Parser for ThemeParser<'_> {
|
||||
type Value = Theme;
|
||||
type Error = ThemeParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (
|
||||
(
|
||||
attention_requested_bg_color,
|
||||
bg_color,
|
||||
bar_bg_color,
|
||||
bar_status_text_color,
|
||||
border_color,
|
||||
captured_focused_title_bg_color,
|
||||
captured_unfocused_title_bg_color,
|
||||
focused_inactive_title_bg_color,
|
||||
focused_inactive_title_text_color,
|
||||
focused_title_bg_color,
|
||||
),
|
||||
(
|
||||
focused_title_text_color,
|
||||
separator_color,
|
||||
unfocused_title_bg_color,
|
||||
unfocused_title_text_color,
|
||||
border_width,
|
||||
title_height,
|
||||
font,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("attention-requested-bg-color")),
|
||||
opt(val("bg-color")),
|
||||
opt(val("bar-bg-color")),
|
||||
opt(val("bar-status-text-color")),
|
||||
opt(val("border-color")),
|
||||
opt(val("captured-focused-title-bg-color")),
|
||||
opt(val("captured-unfocused-title-bg-color")),
|
||||
opt(val("focused-inactive-title-bg-color")),
|
||||
opt(val("focused-inactive-title-text-color")),
|
||||
opt(val("focused-title-bg-color")),
|
||||
),
|
||||
(
|
||||
opt(val("focused-title-text-color")),
|
||||
opt(val("separator-color")),
|
||||
opt(val("unfocused-title-bg-color")),
|
||||
opt(val("unfocused-title-text-color")),
|
||||
recover(opt(s32("border-width"))),
|
||||
recover(opt(s32("title-height"))),
|
||||
recover(opt(str("font"))),
|
||||
),
|
||||
))?;
|
||||
macro_rules! color {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
None => None,
|
||||
Some(v) => match v.parse(&mut ColorParser(self.0)) {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse a color: {}", self.0.error(e));
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(Theme {
|
||||
attention_requested_bg_color: color!(attention_requested_bg_color),
|
||||
bg_color: color!(bg_color),
|
||||
bar_bg_color: color!(bar_bg_color),
|
||||
bar_status_text_color: color!(bar_status_text_color),
|
||||
border_color: color!(border_color),
|
||||
captured_focused_title_bg_color: color!(captured_focused_title_bg_color),
|
||||
captured_unfocused_title_bg_color: color!(captured_unfocused_title_bg_color),
|
||||
focused_inactive_title_bg_color: color!(focused_inactive_title_bg_color),
|
||||
focused_inactive_title_text_color: color!(focused_inactive_title_text_color),
|
||||
focused_title_bg_color: color!(focused_title_bg_color),
|
||||
focused_title_text_color: color!(focused_title_text_color),
|
||||
separator_color: color!(separator_color),
|
||||
unfocused_title_bg_color: color!(unfocused_title_bg_color),
|
||||
unfocused_title_text_color: color!(unfocused_title_text_color),
|
||||
border_width: border_width.despan(),
|
||||
title_height: title_height.despan(),
|
||||
font: font.map(|f| f.value.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
63
toml-config/src/config/spanned.rs
Normal file
63
toml-config/src/config/spanned.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use crate::{
|
||||
config::parser::{ParseResult, Parser},
|
||||
toml::{toml_span::Spanned, toml_value::Value},
|
||||
};
|
||||
|
||||
impl Spanned<&Value> {
|
||||
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
|
||||
self.value.parse(self.span, parser)
|
||||
}
|
||||
|
||||
pub fn parse_map<P: Parser, E>(
|
||||
&self,
|
||||
parser: &mut P,
|
||||
) -> Result<<P as Parser>::Value, Spanned<E>>
|
||||
where
|
||||
<P as Parser>::Error: Into<E>,
|
||||
{
|
||||
self.parse(parser).map_spanned_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned<Value> {
|
||||
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
|
||||
self.as_ref().parse(parser)
|
||||
}
|
||||
|
||||
pub fn parse_map<P: Parser, E>(
|
||||
&self,
|
||||
parser: &mut P,
|
||||
) -> Result<<P as Parser>::Value, Spanned<E>>
|
||||
where
|
||||
<P as Parser>::Error: Into<E>,
|
||||
{
|
||||
self.as_ref().parse_map(parser)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpannedErrorExt {
|
||||
type T;
|
||||
type E;
|
||||
|
||||
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
|
||||
where
|
||||
F: FnOnce(Self::E) -> U;
|
||||
}
|
||||
|
||||
impl<T, E> SpannedErrorExt for Result<T, Spanned<E>> {
|
||||
type T = T;
|
||||
type E = E;
|
||||
|
||||
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
|
||||
where
|
||||
F: FnOnce(Self::E) -> U,
|
||||
{
|
||||
match self {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(Spanned {
|
||||
span: e.span,
|
||||
value: f(e.value),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
17
toml-config/src/config/value.rs
Normal file
17
toml-config/src/config/value.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use crate::{
|
||||
config::parser::{ParseResult, Parser},
|
||||
toml::{toml_span::Span, toml_value::Value},
|
||||
};
|
||||
|
||||
impl Value {
|
||||
pub fn parse<P: Parser>(&self, span: Span, parser: &mut P) -> ParseResult<P> {
|
||||
match self {
|
||||
Value::String(a) => parser.parse_string(span, a),
|
||||
Value::Integer(a) => parser.parse_integer(span, *a),
|
||||
Value::Float(a) => parser.parse_float(span, *a),
|
||||
Value::Boolean(a) => parser.parse_bool(span, *a),
|
||||
Value::Array(a) => parser.parse_array(span, a),
|
||||
Value::Table(a) => parser.parse_table(span, a),
|
||||
}
|
||||
}
|
||||
}
|
||||
75
toml-config/src/default-config.toml
Normal file
75
toml-config/src/default-config.toml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
keymap = """
|
||||
xkb_keymap {
|
||||
xkb_keycodes { include "evdev+aliases(qwerty)" };
|
||||
xkb_types { include "complete" };
|
||||
xkb_compat { include "complete" };
|
||||
xkb_symbols { include "pc+us+inet(evdev)" };
|
||||
};
|
||||
"""
|
||||
|
||||
on-graphics-initialized = { type = "exec", exec = "mako" }
|
||||
|
||||
[shortcuts]
|
||||
alt-h = "focus-left"
|
||||
alt-j = "focus-down"
|
||||
alt-k = "focus-up"
|
||||
alt-l = "focus-right"
|
||||
|
||||
alt-shift-h = "move-left"
|
||||
alt-shift-j = "move-down"
|
||||
alt-shift-k = "move-up"
|
||||
alt-shift-l = "move-right"
|
||||
|
||||
alt-d = "split-horizontal"
|
||||
alt-v = "split-vertical"
|
||||
|
||||
alt-t = "toggle-split"
|
||||
alt-m = "toggle-mono"
|
||||
alt-u = "toggle-fullscreen"
|
||||
|
||||
alt-f = "focus-parent"
|
||||
alt-shift-c = "close"
|
||||
alt-shift-f = "toggle-floating"
|
||||
Super_L = { type = "exec", exec = "alacritty" }
|
||||
alt-p = { type = "exec", exec = "bemenu-run" }
|
||||
alt-q = "quit"
|
||||
alt-shift-r = "reload-config-toml"
|
||||
|
||||
ctrl-alt-F1 = { type = "switch-to-vt", num = 1 }
|
||||
ctrl-alt-F2 = { type = "switch-to-vt", num = 2 }
|
||||
ctrl-alt-F3 = { type = "switch-to-vt", num = 3 }
|
||||
ctrl-alt-F4 = { type = "switch-to-vt", num = 4 }
|
||||
ctrl-alt-F5 = { type = "switch-to-vt", num = 5 }
|
||||
ctrl-alt-F6 = { type = "switch-to-vt", num = 6 }
|
||||
ctrl-alt-F7 = { type = "switch-to-vt", num = 7 }
|
||||
ctrl-alt-F8 = { type = "switch-to-vt", num = 8 }
|
||||
ctrl-alt-F9 = { type = "switch-to-vt", num = 9 }
|
||||
ctrl-alt-F10 = { type = "switch-to-vt", num = 10 }
|
||||
ctrl-alt-F11 = { type = "switch-to-vt", num = 11 }
|
||||
ctrl-alt-F12 = { type = "switch-to-vt", num = 12 }
|
||||
|
||||
alt-F1 = { type = "show-workspace", name = "1" }
|
||||
alt-F2 = { type = "show-workspace", name = "2" }
|
||||
alt-F3 = { type = "show-workspace", name = "3" }
|
||||
alt-F4 = { type = "show-workspace", name = "4" }
|
||||
alt-F5 = { type = "show-workspace", name = "5" }
|
||||
alt-F6 = { type = "show-workspace", name = "6" }
|
||||
alt-F7 = { type = "show-workspace", name = "7" }
|
||||
alt-F8 = { type = "show-workspace", name = "8" }
|
||||
alt-F9 = { type = "show-workspace", name = "9" }
|
||||
alt-F10 = { type = "show-workspace", name = "10" }
|
||||
alt-F11 = { type = "show-workspace", name = "11" }
|
||||
alt-F12 = { type = "show-workspace", name = "12" }
|
||||
|
||||
alt-shift-F1 = { type = "move-to-workspace", name = "1" }
|
||||
alt-shift-F2 = { type = "move-to-workspace", name = "2" }
|
||||
alt-shift-F3 = { type = "move-to-workspace", name = "3" }
|
||||
alt-shift-F4 = { type = "move-to-workspace", name = "4" }
|
||||
alt-shift-F5 = { type = "move-to-workspace", name = "5" }
|
||||
alt-shift-F6 = { type = "move-to-workspace", name = "6" }
|
||||
alt-shift-F7 = { type = "move-to-workspace", name = "7" }
|
||||
alt-shift-F8 = { type = "move-to-workspace", name = "8" }
|
||||
alt-shift-F9 = { type = "move-to-workspace", name = "9" }
|
||||
alt-shift-F10 = { type = "move-to-workspace", name = "10" }
|
||||
alt-shift-F11 = { type = "move-to-workspace", name = "11" }
|
||||
alt-shift-F12 = { type = "move-to-workspace", name = "12" }
|
||||
819
toml-config/src/lib.rs
Normal file
819
toml-config/src/lib.rs
Normal file
|
|
@ -0,0 +1,819 @@
|
|||
#![allow(clippy::len_zero, clippy::single_char_pattern, clippy::collapsible_if)]
|
||||
|
||||
mod config;
|
||||
mod toml;
|
||||
|
||||
use {
|
||||
crate::config::{
|
||||
parse_config, Action, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
|
||||
SimpleCommand, Status, Theme,
|
||||
},
|
||||
ahash::{AHashMap, AHashSet},
|
||||
error_reporter::Report,
|
||||
jay_config::{
|
||||
config, config_dir,
|
||||
exec::{set_env, unset_env, Command},
|
||||
get_workspace,
|
||||
input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat},
|
||||
is_reload,
|
||||
keyboard::{Keymap, ModifiedKeySym},
|
||||
logging::set_log_level,
|
||||
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, set_idle,
|
||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||
switch_to_vt,
|
||||
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
||||
video::{
|
||||
connectors, drm_devices, on_connector_connected, on_graphics_initialized,
|
||||
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
|
||||
Connector, DrmDevice,
|
||||
},
|
||||
},
|
||||
std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc},
|
||||
};
|
||||
|
||||
fn default_seat() -> Seat {
|
||||
get_seat("default")
|
||||
}
|
||||
|
||||
impl Action {
|
||||
fn into_fn(self, state: &Rc<State>) -> Box<dyn FnMut()> {
|
||||
let s = state.persistent.seat;
|
||||
match self {
|
||||
Action::SimpleCommand { cmd } => match cmd {
|
||||
SimpleCommand::Focus(dir) => Box::new(move || s.focus(dir)),
|
||||
SimpleCommand::Move(dir) => Box::new(move || s.move_(dir)),
|
||||
SimpleCommand::Split(axis) => Box::new(move || s.create_split(axis)),
|
||||
SimpleCommand::ToggleSplit => Box::new(move || s.toggle_split()),
|
||||
SimpleCommand::ToggleMono => Box::new(move || s.toggle_mono()),
|
||||
SimpleCommand::ToggleFullscreen => Box::new(move || s.toggle_fullscreen()),
|
||||
SimpleCommand::FocusParent => Box::new(move || s.focus_parent()),
|
||||
SimpleCommand::Close => Box::new(move || s.close()),
|
||||
SimpleCommand::DisablePointerConstraint => {
|
||||
Box::new(move || s.disable_pointer_constraint())
|
||||
}
|
||||
SimpleCommand::ToggleFloating => Box::new(move || s.toggle_floating()),
|
||||
SimpleCommand::Quit => Box::new(quit),
|
||||
SimpleCommand::ReloadConfigToml => {
|
||||
let persistent = state.persistent.clone();
|
||||
Box::new(move || load_config(false, &persistent))
|
||||
}
|
||||
SimpleCommand::ReloadConfigSo => Box::new(reload),
|
||||
SimpleCommand::None => Box::new(|| ()),
|
||||
},
|
||||
Action::Multi { actions } => {
|
||||
let mut actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
Box::new(move || {
|
||||
for action in &mut actions {
|
||||
action();
|
||||
}
|
||||
})
|
||||
}
|
||||
Action::Exec { exec } => Box::new(move || create_command(&exec).spawn()),
|
||||
Action::SwitchToVt { num } => Box::new(move || switch_to_vt(num)),
|
||||
Action::ShowWorkspace { name } => {
|
||||
let workspace = get_workspace(&name);
|
||||
Box::new(move || s.show_workspace(workspace))
|
||||
}
|
||||
Action::ConfigureConnector { con } => Box::new(move || {
|
||||
for c in connectors() {
|
||||
if con.match_.matches(c) {
|
||||
con.apply(c);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Action::ConfigureInput { input } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || {
|
||||
for c in input_devices() {
|
||||
if input.match_.matches(c, &state) {
|
||||
input.apply(c);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Action::ConfigureOutput { out } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || {
|
||||
for c in connectors() {
|
||||
if out.match_.matches(c, &state) {
|
||||
out.apply(c);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Action::SetEnv { env } => Box::new(move || {
|
||||
for (k, v) in &env {
|
||||
set_env(k, v);
|
||||
}
|
||||
}),
|
||||
Action::UnsetEnv { env } => Box::new(move || {
|
||||
for k in &env {
|
||||
unset_env(k);
|
||||
}
|
||||
}),
|
||||
Action::SetKeymap { map } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || state.set_keymap(&map))
|
||||
}
|
||||
Action::SetStatus { status } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || state.set_status(&status))
|
||||
}
|
||||
Action::SetTheme { theme } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || state.apply_theme(&theme))
|
||||
}
|
||||
Action::SetLogLevel { level } => Box::new(move || set_log_level(level)),
|
||||
Action::SetGfxApi { api } => Box::new(move || set_gfx_api(api)),
|
||||
Action::ConfigureDirectScanout { enabled } => {
|
||||
Box::new(move || set_direct_scanout_enabled(enabled))
|
||||
}
|
||||
Action::ConfigureDrmDevice { dev } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || {
|
||||
for d in drm_devices() {
|
||||
if dev.match_.matches(d, &state) {
|
||||
dev.apply(d);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Action::SetRenderDevice { dev } => {
|
||||
let state = state.clone();
|
||||
Box::new(move || {
|
||||
for d in drm_devices() {
|
||||
if dev.matches(d, &state) {
|
||||
d.make_render_device();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Action::ConfigureIdle { idle } => Box::new(move || set_idle(Some(idle))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_recursive_match<'a, U>(
|
||||
type_name: &str,
|
||||
list: &'a AHashMap<String, U>,
|
||||
active: &mut AHashSet<&'a str>,
|
||||
name: &'a str,
|
||||
matches: impl FnOnce(&'a U, &mut AHashSet<&'a str>) -> bool,
|
||||
) -> bool {
|
||||
match list.get(name) {
|
||||
None => {
|
||||
log::warn!("{type_name} with name {name} does not exist");
|
||||
false
|
||||
}
|
||||
Some(m) => {
|
||||
if active.insert(name) {
|
||||
let matches = matches(m, active);
|
||||
active.remove(name);
|
||||
matches
|
||||
} else {
|
||||
log::warn!("Recursion while evaluating match for {type_name} {name}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigDrmDevice {
|
||||
fn apply(&self, d: DrmDevice) {
|
||||
if let Some(api) = self.gfx_api {
|
||||
d.set_gfx_api(api);
|
||||
}
|
||||
if let Some(dse) = self.direct_scanout_enabled {
|
||||
d.set_direct_scanout_enabled(dse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrmDeviceMatch {
|
||||
fn matches(&self, d: DrmDevice, state: &State) -> bool {
|
||||
self.matches_(d, state, &mut AHashSet::new())
|
||||
}
|
||||
|
||||
fn matches_<'a>(
|
||||
&'a self,
|
||||
d: DrmDevice,
|
||||
state: &'a State,
|
||||
active: &mut AHashSet<&'a str>,
|
||||
) -> bool {
|
||||
match self {
|
||||
DrmDeviceMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
|
||||
DrmDeviceMatch::All {
|
||||
name,
|
||||
syspath,
|
||||
vendor,
|
||||
vendor_name,
|
||||
model,
|
||||
model_name,
|
||||
devnode,
|
||||
} => {
|
||||
if let Some(name) = name {
|
||||
let matches = apply_recursive_match(
|
||||
"drm device",
|
||||
&state.drm_devices,
|
||||
active,
|
||||
name,
|
||||
|m, active| m.matches_(d, state, active),
|
||||
);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(syspath) = syspath {
|
||||
if d.syspath() != *syspath {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(devnode) = devnode {
|
||||
if d.devnode() != *devnode {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(model) = model_name {
|
||||
if d.model() != *model {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(vendor) = vendor_name {
|
||||
if d.vendor() != *vendor {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(vendor) = vendor {
|
||||
if d.pci_id().vendor != *vendor {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(model) = model {
|
||||
if d.pci_id().model != *model {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputMatch {
|
||||
fn matches(&self, d: InputDevice, state: &State) -> bool {
|
||||
self.matches_(d, state, &mut AHashSet::new())
|
||||
}
|
||||
|
||||
fn matches_<'a>(
|
||||
&'a self,
|
||||
d: InputDevice,
|
||||
state: &'a State,
|
||||
active: &mut AHashSet<&'a str>,
|
||||
) -> bool {
|
||||
match self {
|
||||
InputMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
|
||||
InputMatch::All {
|
||||
tag,
|
||||
name,
|
||||
syspath,
|
||||
devnode,
|
||||
is_keyboard,
|
||||
is_pointer,
|
||||
is_touch,
|
||||
is_tablet_tool,
|
||||
is_tablet_pad,
|
||||
is_gesture,
|
||||
is_switch,
|
||||
} => {
|
||||
if let Some(name) = name {
|
||||
if d.name() != *name {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(tag) = tag {
|
||||
let matches = apply_recursive_match(
|
||||
"input device",
|
||||
&state.input_devices,
|
||||
active,
|
||||
tag,
|
||||
|m, active| m.matches_(d, state, active),
|
||||
);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(syspath) = syspath {
|
||||
if d.syspath() != *syspath {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(devnode) = devnode {
|
||||
if d.devnode() != *devnode {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
macro_rules! check_cap {
|
||||
($is:expr, $cap:ident) => {
|
||||
if let Some(is) = *$is {
|
||||
if d.has_capability(jay_config::input::capability::$cap) != is {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
check_cap!(is_keyboard, CAP_KEYBOARD);
|
||||
check_cap!(is_pointer, CAP_POINTER);
|
||||
check_cap!(is_touch, CAP_TOUCH);
|
||||
check_cap!(is_tablet_tool, CAP_TABLET_TOOL);
|
||||
check_cap!(is_tablet_pad, CAP_TABLET_PAD);
|
||||
check_cap!(is_gesture, CAP_GESTURE);
|
||||
check_cap!(is_switch, CAP_SWITCH);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
fn apply(&self, c: InputDevice) {
|
||||
if let Some(v) = self.accel_profile {
|
||||
c.set_accel_profile(v);
|
||||
}
|
||||
if let Some(v) = self.accel_speed {
|
||||
c.set_accel_speed(v);
|
||||
}
|
||||
if let Some(v) = self.tap_enabled {
|
||||
c.set_tap_enabled(v);
|
||||
}
|
||||
if let Some(v) = self.tap_drag_enabled {
|
||||
c.set_drag_enabled(v);
|
||||
}
|
||||
if let Some(v) = self.tap_drag_lock_enabled {
|
||||
c.set_drag_lock_enabled(v);
|
||||
}
|
||||
if let Some(v) = self.left_handed {
|
||||
c.set_left_handed(v);
|
||||
}
|
||||
if let Some(v) = self.natural_scrolling {
|
||||
c.set_natural_scrolling_enabled(v);
|
||||
}
|
||||
if let Some(v) = self.px_per_wheel_scroll {
|
||||
c.set_px_per_wheel_scroll(v);
|
||||
}
|
||||
if let Some(v) = self.transform_matrix {
|
||||
c.set_transform_matrix(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputMatch {
|
||||
fn matches(&self, c: Connector, state: &State) -> bool {
|
||||
if !c.connected() {
|
||||
return false;
|
||||
}
|
||||
self.matches_(c, state, &mut AHashSet::new())
|
||||
}
|
||||
|
||||
fn matches_<'a>(
|
||||
&'a self,
|
||||
c: Connector,
|
||||
state: &'a State,
|
||||
active: &mut AHashSet<&'a str>,
|
||||
) -> bool {
|
||||
match self {
|
||||
OutputMatch::Any(m) => m.iter().any(|m| m.matches_(c, state, active)),
|
||||
OutputMatch::All {
|
||||
name,
|
||||
connector,
|
||||
serial_number,
|
||||
manufacturer,
|
||||
model,
|
||||
} => {
|
||||
if let Some(name) = name {
|
||||
let matches = apply_recursive_match(
|
||||
"output",
|
||||
&state.outputs,
|
||||
active,
|
||||
name,
|
||||
|m, active| m.matches_(c, state, active),
|
||||
);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(connector) = &connector {
|
||||
if c.name() != *connector {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(serial_number) = &serial_number {
|
||||
if c.serial_number() != *serial_number {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(manufacturer) = &manufacturer {
|
||||
if c.manufacturer() != *manufacturer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(model) = &model {
|
||||
if c.model() != *model {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorMatch {
|
||||
fn matches(&self, c: Connector) -> bool {
|
||||
if !c.exists() {
|
||||
return false;
|
||||
}
|
||||
match self {
|
||||
ConnectorMatch::Any(m) => m.iter().any(|m| m.matches(c)),
|
||||
ConnectorMatch::All { connector } => {
|
||||
if let Some(connector) = &connector {
|
||||
if c.name() != *connector {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigConnector {
|
||||
fn apply(&self, c: Connector) {
|
||||
c.set_enabled(self.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
impl Output {
|
||||
fn apply(&self, c: Connector) {
|
||||
if self.x.is_some() || self.y.is_some() {
|
||||
let (old_x, old_y) = c.position();
|
||||
c.set_position(self.x.unwrap_or(old_x), self.y.unwrap_or(old_y));
|
||||
}
|
||||
if let Some(scale) = self.scale {
|
||||
c.set_scale(scale);
|
||||
}
|
||||
if let Some(transform) = self.transform {
|
||||
c.set_transform(transform);
|
||||
}
|
||||
if let Some(mode) = &self.mode {
|
||||
let modes = c.modes();
|
||||
let m = modes.iter().find(|m| {
|
||||
if m.width() != mode.width || m.height() != mode.height {
|
||||
return false;
|
||||
}
|
||||
match mode.refresh_rate {
|
||||
None => true,
|
||||
Some(rr) => m.refresh_rate() as f64 / 1000.0 == rr,
|
||||
}
|
||||
});
|
||||
match m {
|
||||
None => {
|
||||
log::warn!("Output {} does not support mode {mode}", c.name());
|
||||
}
|
||||
Some(m) => c.set_mode(m.width(), m.height(), Some(m.refresh_rate())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
outputs: AHashMap<String, OutputMatch>,
|
||||
drm_devices: AHashMap<String, DrmDeviceMatch>,
|
||||
input_devices: AHashMap<String, InputMatch>,
|
||||
persistent: Rc<PersistentState>,
|
||||
keymaps: AHashMap<String, Keymap>,
|
||||
}
|
||||
|
||||
impl Drop for State {
|
||||
fn drop(&mut self) {
|
||||
for keymap in self.keymaps.values() {
|
||||
keymap.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn unbind_all(&self) {
|
||||
let mut binds = self.persistent.binds.borrow_mut();
|
||||
for bind in binds.drain() {
|
||||
self.persistent.seat.unbind(bind);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_shortcuts(
|
||||
self: &Rc<Self>,
|
||||
shortcuts: impl IntoIterator<Item = (ModifiedKeySym, Action)>,
|
||||
) {
|
||||
let mut binds = self.persistent.binds.borrow_mut();
|
||||
for (key, value) in shortcuts {
|
||||
if let Action::SimpleCommand {
|
||||
cmd: SimpleCommand::None,
|
||||
} = value
|
||||
{
|
||||
self.persistent.seat.unbind(key);
|
||||
binds.remove(&key);
|
||||
} else {
|
||||
self.persistent.seat.bind(key, value.into_fn(self));
|
||||
binds.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_keymap(&self, map: &ConfigKeymap) {
|
||||
let map = match map {
|
||||
ConfigKeymap::Named(n) => match self.keymaps.get(n) {
|
||||
None => {
|
||||
log::warn!("Unknown keymap {n}");
|
||||
return;
|
||||
}
|
||||
Some(m) => *m,
|
||||
},
|
||||
ConfigKeymap::Defined { map, .. } => *map,
|
||||
ConfigKeymap::Literal(map) => *map,
|
||||
};
|
||||
self.persistent.seat.set_keymap(map);
|
||||
}
|
||||
|
||||
fn set_status(&self, status: &Option<Status>) {
|
||||
set_status("");
|
||||
match status {
|
||||
None => unset_status_command(),
|
||||
Some(s) => {
|
||||
set_i3bar_separator(s.separator.as_deref().unwrap_or(" | "));
|
||||
set_status_command(s.format, create_command(&s.exec))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_theme(&self, theme: &Theme) {
|
||||
use jay_config::theme::{colors::*, sized::*};
|
||||
macro_rules! color {
|
||||
($colorable:ident, $field:ident) => {
|
||||
if let Some(color) = theme.$field {
|
||||
$colorable.set_color(color)
|
||||
}
|
||||
};
|
||||
}
|
||||
color!(
|
||||
ATTENTION_REQUESTED_BACKGROUND_COLOR,
|
||||
attention_requested_bg_color
|
||||
);
|
||||
color!(BACKGROUND_COLOR, bg_color);
|
||||
color!(BAR_BACKGROUND_COLOR, bar_bg_color);
|
||||
color!(BAR_STATUS_TEXT_COLOR, bar_status_text_color);
|
||||
color!(BORDER_COLOR, border_color);
|
||||
color!(
|
||||
CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
|
||||
captured_focused_title_bg_color
|
||||
);
|
||||
color!(
|
||||
CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
|
||||
captured_unfocused_title_bg_color
|
||||
);
|
||||
color!(
|
||||
FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
|
||||
focused_inactive_title_bg_color
|
||||
);
|
||||
color!(
|
||||
FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
|
||||
focused_inactive_title_text_color
|
||||
);
|
||||
color!(FOCUSED_TITLE_BACKGROUND_COLOR, focused_title_bg_color);
|
||||
color!(FOCUSED_TITLE_TEXT_COLOR, focused_title_text_color);
|
||||
color!(SEPARATOR_COLOR, separator_color);
|
||||
color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
|
||||
color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
|
||||
macro_rules! size {
|
||||
($sized:ident, $field:ident) => {
|
||||
if let Some(size) = theme.$field {
|
||||
$sized.set(size);
|
||||
}
|
||||
};
|
||||
}
|
||||
size!(BORDER_WIDTH, border_width);
|
||||
size!(TITLE_HEIGHT, title_height);
|
||||
if let Some(font) = &theme.font {
|
||||
set_font(font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
struct OutputId {
|
||||
manufacturer: String,
|
||||
model: String,
|
||||
serial_number: String,
|
||||
}
|
||||
|
||||
struct PersistentState {
|
||||
seen_outputs: RefCell<AHashSet<OutputId>>,
|
||||
default: Config,
|
||||
seat: Seat,
|
||||
binds: RefCell<AHashSet<ModifiedKeySym>>,
|
||||
}
|
||||
|
||||
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||
let mut path = PathBuf::from(config_dir());
|
||||
path.push("config.toml");
|
||||
let config = match std::fs::read(&path) {
|
||||
Ok(input) => match parse_config(&input, |e| {
|
||||
log::warn!("Error while parsing {}: {}", path.display(), Report::new(e))
|
||||
}) {
|
||||
None if initial_load => {
|
||||
log::warn!("Using default config instead");
|
||||
persistent.default.clone()
|
||||
}
|
||||
None => {
|
||||
log::warn!("Ignoring config reload");
|
||||
return;
|
||||
}
|
||||
Some(c) => c,
|
||||
},
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
log::info!("{} does not exist. Using default config.", path.display());
|
||||
persistent.default.clone()
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Could not load {}: {}", path.display(), Report::new(e));
|
||||
log::warn!("Ignoring config reload");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut outputs = AHashMap::new();
|
||||
for output in &config.outputs {
|
||||
if let Some(name) = &output.name {
|
||||
let prev = outputs.insert(name.clone(), output.match_.clone());
|
||||
if prev.is_some() {
|
||||
log::warn!("Duplicate output name {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut keymaps = AHashMap::new();
|
||||
for keymap in config.keymaps {
|
||||
match keymap {
|
||||
ConfigKeymap::Defined { name, map } => {
|
||||
keymaps.insert(name, map);
|
||||
}
|
||||
_ => log::warn!("Keymap is not in defined form in top-level context"),
|
||||
}
|
||||
}
|
||||
let mut input_devices = AHashMap::new();
|
||||
for input in &config.inputs {
|
||||
if let Some(tag) = &input.tag {
|
||||
let prev = input_devices.insert(tag.clone(), input.match_.clone());
|
||||
if prev.is_some() {
|
||||
log::warn!("Duplicate input tag {tag}");
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut named_drm_device = AHashMap::new();
|
||||
for drm_device in &config.drm_devices {
|
||||
if let Some(name) = &drm_device.name {
|
||||
let prev = named_drm_device.insert(name.clone(), drm_device.match_.clone());
|
||||
if prev.is_some() {
|
||||
log::warn!("Duplicate drm device name {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
let state = Rc::new(State {
|
||||
outputs,
|
||||
drm_devices: named_drm_device,
|
||||
input_devices,
|
||||
persistent: persistent.clone(),
|
||||
keymaps,
|
||||
});
|
||||
state.set_status(&config.status);
|
||||
match config.on_graphics_initialized {
|
||||
None => on_graphics_initialized(|| ()),
|
||||
Some(a) => on_graphics_initialized(a.into_fn(&state)),
|
||||
}
|
||||
match config.on_idle {
|
||||
None => on_idle(|| ()),
|
||||
Some(a) => on_idle(a.into_fn(&state)),
|
||||
}
|
||||
state.unbind_all();
|
||||
state.apply_shortcuts(config.shortcuts);
|
||||
if let Some(keymap) = config.keymap {
|
||||
state.set_keymap(&keymap);
|
||||
}
|
||||
on_new_connector(move |c| {
|
||||
for connector in &config.connectors {
|
||||
if connector.match_.matches(c) {
|
||||
connector.apply(c);
|
||||
}
|
||||
}
|
||||
});
|
||||
on_connector_connected({
|
||||
let state = state.clone();
|
||||
move |c| {
|
||||
let id = OutputId {
|
||||
manufacturer: c.manufacturer(),
|
||||
model: c.model(),
|
||||
serial_number: c.serial_number(),
|
||||
};
|
||||
if state.persistent.seen_outputs.borrow_mut().insert(id) {
|
||||
for output in &config.outputs {
|
||||
if output.match_.matches(c, &state) {
|
||||
output.apply(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
set_default_workspace_capture(config.workspace_capture);
|
||||
for (k, v) in config.env {
|
||||
set_env(&k, &v);
|
||||
}
|
||||
if initial_load && !is_reload() {
|
||||
if let Some(on_startup) = config.on_startup {
|
||||
on_startup.into_fn(&state)();
|
||||
}
|
||||
if let Some(level) = config.log_level {
|
||||
set_log_level(level);
|
||||
}
|
||||
if let Some(idle) = config.idle {
|
||||
set_idle(Some(idle));
|
||||
}
|
||||
}
|
||||
on_devices_enumerated({
|
||||
let state = state.clone();
|
||||
move || {
|
||||
if let Some(dev) = config.render_device {
|
||||
for d in drm_devices() {
|
||||
if dev.matches(d, &state) {
|
||||
d.make_render_device();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
reset_colors();
|
||||
reset_font();
|
||||
reset_sizes();
|
||||
state.apply_theme(&config.theme);
|
||||
if let Some(api) = config.gfx_api {
|
||||
set_gfx_api(api);
|
||||
}
|
||||
if let Some(dse) = config.direct_scanout_enabled {
|
||||
set_direct_scanout_enabled(dse);
|
||||
}
|
||||
on_new_drm_device({
|
||||
let state = state.clone();
|
||||
move |d| {
|
||||
for dev in &config.drm_devices {
|
||||
if dev.match_.matches(d, &state) {
|
||||
dev.apply(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
on_new_input_device({
|
||||
let state = state.clone();
|
||||
move |c| {
|
||||
for input in &config.inputs {
|
||||
if input.match_.matches(c, &state) {
|
||||
input.apply(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_command(exec: &Exec) -> Command {
|
||||
let mut command = Command::new(&exec.prog);
|
||||
for arg in &exec.args {
|
||||
command.arg(arg);
|
||||
}
|
||||
for (k, v) in &exec.envs {
|
||||
command.env(k, v);
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
const DEFAULT: &[u8] = include_bytes!("default-config.toml");
|
||||
|
||||
pub fn configure() {
|
||||
let default = parse_config(DEFAULT, |e| {
|
||||
panic!("Could not parse the default config: {}", Report::new(e))
|
||||
});
|
||||
let persistent = Rc::new(PersistentState {
|
||||
seen_outputs: Default::default(),
|
||||
default: default.unwrap(),
|
||||
seat: default_seat(),
|
||||
binds: Default::default(),
|
||||
});
|
||||
load_config(true, &persistent);
|
||||
}
|
||||
|
||||
config!(configure);
|
||||
6
toml-config/src/toml.rs
Normal file
6
toml-config/src/toml.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod toml_lexer;
|
||||
pub mod toml_parser;
|
||||
pub mod toml_span;
|
||||
pub mod toml_value;
|
||||
133
toml-config/src/toml/tests.rs
Normal file
133
toml-config/src/toml/tests.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use {
|
||||
crate::{
|
||||
config::error::SpannedError,
|
||||
toml::{
|
||||
toml_parser::{parse, ErrorHandler, ParserError},
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
bstr::{BStr, ByteSlice},
|
||||
std::{
|
||||
convert::Infallible,
|
||||
os::unix::ffi::OsStrExt,
|
||||
panic::{catch_unwind, AssertUnwindSafe},
|
||||
str::FromStr,
|
||||
},
|
||||
walkdir::WalkDir,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut have_failures = false;
|
||||
let mut num = 0;
|
||||
for path in WalkDir::new("./toml-test/tests/valid") {
|
||||
let path = path.unwrap();
|
||||
if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") {
|
||||
num += 1;
|
||||
let res = catch_unwind(AssertUnwindSafe(|| {
|
||||
have_failures |= run_test(prefix.as_bstr());
|
||||
}));
|
||||
if res.is_err() {
|
||||
eprintln!("panic while running {}", prefix.as_bstr());
|
||||
}
|
||||
}
|
||||
}
|
||||
if have_failures {
|
||||
panic!("There were test failures");
|
||||
}
|
||||
eprintln!("ran {num} tests");
|
||||
}
|
||||
|
||||
fn run_test(prefix: &BStr) -> bool {
|
||||
let toml = std::fs::read(&format!("{}.toml", prefix)).unwrap();
|
||||
let json = std::fs::read_to_string(&format!("{}.json", prefix)).unwrap();
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
let json_as_toml = json_to_value(json);
|
||||
let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix, &toml)) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("toml could not be parsed in test {}", prefix);
|
||||
NoErrorHandler(prefix, &toml).handle(e);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if toml != json_as_toml {
|
||||
eprintln!("toml and json differ in test {}", prefix);
|
||||
eprintln!("toml: {:#?}", toml);
|
||||
eprintln!("json: {:#?}", json_as_toml);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn json_to_value(json: serde_json::Value) -> Spanned<Value> {
|
||||
let span = Span { lo: 0, hi: 0 };
|
||||
let val = match json {
|
||||
serde_json::Value::String(_)
|
||||
| serde_json::Value::Number(_)
|
||||
| serde_json::Value::Null
|
||||
| serde_json::Value::Bool(_) => panic!("Unexpected type"),
|
||||
serde_json::Value::Array(v) => Value::Array(v.into_iter().map(json_to_value).collect()),
|
||||
serde_json::Value::Object(v) => {
|
||||
if v.len() == 2 && v.contains_key("type") && v.contains_key("value") {
|
||||
let ty = v.get("type").unwrap().as_str().unwrap();
|
||||
let val = v.get("value").unwrap().as_str().unwrap();
|
||||
match ty {
|
||||
"string" => Value::String(val.to_owned()),
|
||||
"integer" => Value::Integer(i64::from_str(val).unwrap()),
|
||||
"float" => Value::Float(f64::from_str(val).unwrap()),
|
||||
"bool" => Value::Boolean(bool::from_str(val).unwrap()),
|
||||
_ => panic!("unexpected type {}", ty),
|
||||
}
|
||||
} else {
|
||||
Value::Table(
|
||||
v.into_iter()
|
||||
.map(|(k, v)| (k.spanned(span), json_to_value(v)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
val.spanned(span)
|
||||
}
|
||||
|
||||
struct NoErrorHandler<'a>(&'a BStr, &'a [u8]);
|
||||
|
||||
impl<'a> ErrorHandler for NoErrorHandler<'a> {
|
||||
fn handle(&self, err: Spanned<ParserError>) {
|
||||
eprintln!(
|
||||
"{}: An error occurred during validation: {}",
|
||||
self.0,
|
||||
SpannedError {
|
||||
input: self.1.into(),
|
||||
span: err.span,
|
||||
cause: Some(err.value),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
|
||||
eprintln!(
|
||||
"{}: Redefinition: {}",
|
||||
self.0,
|
||||
SpannedError {
|
||||
input: self.1.into(),
|
||||
span: err.span,
|
||||
cause: Some(err.value),
|
||||
}
|
||||
);
|
||||
eprintln!(
|
||||
"{}: Previous: {}",
|
||||
self.0,
|
||||
SpannedError {
|
||||
input: self.1.into(),
|
||||
span: prev,
|
||||
cause: None::<Infallible>,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
193
toml-config/src/toml/toml_lexer.rs
Normal file
193
toml-config/src/toml/toml_lexer.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use crate::toml::toml_span::{Span, Spanned, SpannedExt};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Token<'a> {
|
||||
Dot,
|
||||
Equals,
|
||||
Comma,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LiteralString(&'a [u8]),
|
||||
CookedString(&'a [u8]),
|
||||
Literal(&'a [u8]),
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
pub fn name(self, value_context: bool) -> &'static str {
|
||||
match self {
|
||||
Token::Dot => "`.`",
|
||||
Token::Equals => "`=`",
|
||||
Token::Comma => "`,`",
|
||||
Token::LeftBracket => "`[`",
|
||||
Token::RightBracket => "`]`",
|
||||
Token::LeftBrace => "`{`",
|
||||
Token::RightBrace => "`}`",
|
||||
Token::LiteralString(_) | Token::CookedString(_) => "a string",
|
||||
Token::Literal(_) if value_context => "a literal",
|
||||
Token::Literal(_) => "a key",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
input: &'a [u8],
|
||||
pos: usize,
|
||||
peek: Option<Spanned<Token<'a>>>,
|
||||
peek_value_context: bool,
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(input: &'a [u8]) -> Self {
|
||||
Self {
|
||||
input,
|
||||
pos: 0,
|
||||
peek: None,
|
||||
peek_value_context: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pos(&mut self) -> usize {
|
||||
self.skip_ws();
|
||||
self.pos
|
||||
}
|
||||
|
||||
fn skip_ws(&mut self) {
|
||||
while let Some(char) = self.input.get(self.pos).copied() {
|
||||
match char {
|
||||
b' ' | b'\t' | b'\n' => self.pos += 1,
|
||||
b'#' => {
|
||||
self.pos += 1;
|
||||
while let Some(char) = self.input.get(self.pos).copied() {
|
||||
self.pos += 1;
|
||||
if char == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
|
||||
let next = self.next(value_context);
|
||||
self.peek = next;
|
||||
self.peek_value_context = value_context;
|
||||
next
|
||||
}
|
||||
|
||||
pub fn next(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
|
||||
if let Some(peek) = self.peek.take() {
|
||||
if self.peek_value_context == value_context {
|
||||
return Some(peek);
|
||||
}
|
||||
self.pos = peek.span.lo;
|
||||
}
|
||||
|
||||
use Token::*;
|
||||
|
||||
macro_rules! get {
|
||||
($off:expr) => {
|
||||
self.input.get(self.pos + $off).copied()
|
||||
};
|
||||
}
|
||||
|
||||
self.skip_ws();
|
||||
|
||||
let Some(c) = get!(0) else {
|
||||
return None;
|
||||
};
|
||||
let pos = self.pos;
|
||||
|
||||
macro_rules! span {
|
||||
() => {
|
||||
Span {
|
||||
lo: pos,
|
||||
hi: self.pos,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
'simple: {
|
||||
let t = match c {
|
||||
b'.' => Dot,
|
||||
b',' => Comma,
|
||||
b'=' => Equals,
|
||||
b'[' => LeftBracket,
|
||||
b']' => RightBracket,
|
||||
b'{' => LeftBrace,
|
||||
b'}' => RightBrace,
|
||||
_ => break 'simple,
|
||||
};
|
||||
self.pos += 1;
|
||||
return Some(t.spanned(span!()));
|
||||
}
|
||||
|
||||
macro_rules! try_string {
|
||||
($delim:expr, $escaping:expr, $ident:ident) => {
|
||||
if c == $delim {
|
||||
'ml_string: {
|
||||
let delim = ($delim, Some($delim), Some($delim));
|
||||
if (c, get!(1), get!(2)) != delim {
|
||||
break 'ml_string;
|
||||
}
|
||||
self.pos += 3;
|
||||
if get!(0) == Some(b'\n') {
|
||||
self.pos += 1;
|
||||
}
|
||||
let start = self.pos;
|
||||
let end = loop {
|
||||
let c = match get!(0) {
|
||||
Some(c) => c,
|
||||
_ => break self.pos,
|
||||
};
|
||||
self.pos += 1;
|
||||
if $escaping && c == b'\\' {
|
||||
self.pos += 1;
|
||||
} else if c == $delim {
|
||||
if (c, get!(0), get!(1)) == delim && get!(2) != Some($delim) {
|
||||
self.pos += 2;
|
||||
break self.pos - 3;
|
||||
}
|
||||
}
|
||||
};
|
||||
return Some($ident(&self.input[start..end]).spanned(span!()));
|
||||
}
|
||||
self.pos += 1;
|
||||
let start = self.pos;
|
||||
let end = loop {
|
||||
let c = match get!(0) {
|
||||
Some(c) => c,
|
||||
_ => break self.pos,
|
||||
};
|
||||
self.pos += 1;
|
||||
if $escaping && c == b'\\' {
|
||||
self.pos += 1;
|
||||
} else if c == $delim {
|
||||
break self.pos - 1;
|
||||
}
|
||||
};
|
||||
return Some($ident(&self.input[start..end]).spanned(span!()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try_string!(b'\'', false, LiteralString);
|
||||
try_string!(b'"', true, CookedString);
|
||||
|
||||
let start = self.pos;
|
||||
while let Some(c) = get!(0) {
|
||||
match c {
|
||||
b' ' | b'\t' | b'\n' | b'#' | b',' | b'=' | b'{' | b'}' | b'[' | b']' => break,
|
||||
b'.' if !value_context => break,
|
||||
_ => {}
|
||||
}
|
||||
self.pos += 1;
|
||||
}
|
||||
let end = self.pos;
|
||||
|
||||
Some(Literal(&self.input[start..end]).spanned(span!()))
|
||||
}
|
||||
}
|
||||
534
toml-config/src/toml/toml_parser.rs
Normal file
534
toml-config/src/toml/toml_parser.rs
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
use {
|
||||
crate::toml::{
|
||||
toml_lexer::{Lexer, Token},
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
bstr::ByteSlice,
|
||||
indexmap::{
|
||||
map::{raw_entry_v1::RawEntryMut, RawEntryApiV1},
|
||||
IndexMap,
|
||||
},
|
||||
std::{collections::VecDeque, mem, str::FromStr},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub trait ErrorHandler {
|
||||
fn handle(&self, err: Spanned<ParserError>);
|
||||
fn redefinition(&self, err: Spanned<ParserError>, prev: Span);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParserError {
|
||||
#[error("Unexpected end of file")]
|
||||
UnexpectedEof,
|
||||
#[error("Expected a key")]
|
||||
MissingKey,
|
||||
#[error("Expected {0} but found {1}")]
|
||||
Expected(&'static str, &'static str),
|
||||
#[error("Duplicate key overwrites the previous definition")]
|
||||
Redefined,
|
||||
#[error("Literal is not valid UTF-8")]
|
||||
NonUtf8Literal,
|
||||
#[error("Could not parse the literal")]
|
||||
UnknownLiteral,
|
||||
#[error("Ignoring key due to following error")]
|
||||
IgnoringKey,
|
||||
#[error("Unnecessary comma")]
|
||||
UnnecessaryComma,
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
input: &[u8],
|
||||
error_handler: &dyn ErrorHandler,
|
||||
) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let parser = Parser {
|
||||
lexer: Lexer::new(input),
|
||||
error_handler,
|
||||
last_span: None,
|
||||
};
|
||||
parser.parse()
|
||||
}
|
||||
|
||||
struct Parser<'a, 'b> {
|
||||
lexer: Lexer<'a>,
|
||||
error_handler: &'b dyn ErrorHandler,
|
||||
last_span: Option<Span>,
|
||||
}
|
||||
|
||||
type Key = VecDeque<Spanned<String>>;
|
||||
|
||||
impl<'a, 'b> Parser<'a, 'b> {
|
||||
fn parse(mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
self.parse_document()
|
||||
}
|
||||
|
||||
fn unexpected_eof(&self) -> Spanned<ParserError> {
|
||||
let span = self.last_span.unwrap_or(Span { lo: 0, hi: 0 });
|
||||
ParserError::UnexpectedEof.spanned(span)
|
||||
}
|
||||
|
||||
fn next(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
|
||||
match self.lexer.next(value_context) {
|
||||
Some(t) => {
|
||||
self.last_span = Some(t.span);
|
||||
Ok(t)
|
||||
}
|
||||
_ => Err(self.unexpected_eof()),
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
|
||||
match self.lexer.peek(value_context) {
|
||||
Some(t) => Ok(t),
|
||||
_ => Err(self.unexpected_eof()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let token = self.peek(true)?;
|
||||
match token.value {
|
||||
Token::LiteralString(s) => self.parse_literal_string(s),
|
||||
Token::CookedString(s) => self.parse_cooked_string(s),
|
||||
Token::LeftBracket => self.parse_array(),
|
||||
Token::Literal(l) => self.parse_literal_value(l),
|
||||
Token::LeftBrace => self.parse_inline_table(),
|
||||
Token::Dot | Token::Equals | Token::Comma | Token::RightBrace | Token::RightBracket => {
|
||||
Err(ParserError::Expected("a value", token.value.name(true)).spanned(token.span))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_literal_value(
|
||||
&mut self,
|
||||
literal: &[u8],
|
||||
) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let span = self.next(true)?.span;
|
||||
let Ok(s) = std::str::from_utf8(literal) else {
|
||||
return Err(ParserError::NonUtf8Literal.spanned(span));
|
||||
};
|
||||
if s == "true" {
|
||||
return Ok(Value::Boolean(true).spanned(span));
|
||||
}
|
||||
if s == "false" {
|
||||
return Ok(Value::Boolean(false).spanned(span));
|
||||
}
|
||||
let s = s.replace('_', "");
|
||||
if let Ok(n) = i64::from_str(&s) {
|
||||
return Ok(Value::Integer(n).spanned(span));
|
||||
}
|
||||
'radix: {
|
||||
let b = s.as_bytes();
|
||||
if b.len() >= 2 && b[0] == b'0' {
|
||||
let radix = match b[1] {
|
||||
b'x' => 16,
|
||||
b'o' => 8,
|
||||
b'b' => 2,
|
||||
_ => break 'radix,
|
||||
};
|
||||
if let Ok(n) = i64::from_str_radix(&s[2..], radix) {
|
||||
return Ok(Value::Integer(n).spanned(span));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(n) = f64::from_str(&s) {
|
||||
return Ok(Value::Float(n).spanned(span));
|
||||
}
|
||||
Err(ParserError::UnknownLiteral.spanned(span))
|
||||
}
|
||||
|
||||
fn parse_literal_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let span = self.next(true)?.span;
|
||||
let s = s.as_bstr().to_string();
|
||||
Ok(Value::String(s).spanned(span))
|
||||
}
|
||||
|
||||
fn parse_cooked_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let span = self.next(true)?.span;
|
||||
let s = self.cook_string(s);
|
||||
Ok(Value::String(s).spanned(span))
|
||||
}
|
||||
|
||||
fn cook_string(&self, s: &[u8]) -> String {
|
||||
use std::io::Write;
|
||||
|
||||
if !s.contains(&b'\\') {
|
||||
return s.as_bstr().to_string();
|
||||
}
|
||||
let mut res = vec![];
|
||||
let mut pos = 0;
|
||||
while pos < s.len() {
|
||||
let c = s[pos];
|
||||
pos += 1;
|
||||
match c {
|
||||
b'\\' => {
|
||||
let c = s[pos];
|
||||
pos += 1;
|
||||
match c {
|
||||
b'\\' => res.push(b'\\'),
|
||||
b'"' => res.push(b'"'),
|
||||
b'b' => res.push(0x8),
|
||||
b'f' => res.push(0xc),
|
||||
b'n' => res.push(b'\n'),
|
||||
b'r' => res.push(b'\r'),
|
||||
b't' => res.push(b'\t'),
|
||||
b'e' => res.push(0x1b),
|
||||
b'x' | b'u' | b'U' => 'unicode: {
|
||||
let len = match c {
|
||||
b'x' => 2,
|
||||
b'u' => 4,
|
||||
_ => 8,
|
||||
};
|
||||
if s.len() - pos >= len {
|
||||
if let Ok(s) = std::str::from_utf8(&s[pos..pos + len]) {
|
||||
if let Ok(n) = u32::from_str_radix(s, 16) {
|
||||
if let Some(c) = char::from_u32(n) {
|
||||
pos += len;
|
||||
let _ = write!(res, "{}", c);
|
||||
break 'unicode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res.extend_from_slice(&s[pos - 2..]);
|
||||
}
|
||||
b' ' | b'\t' | b'\n' => {
|
||||
let mut t = pos;
|
||||
let mut saw_nl = c == b'\n';
|
||||
while t < s.len() && matches!(s[t], b' ' | b'\t' | b'\n') {
|
||||
saw_nl |= s[t] == b'\n';
|
||||
t += 1;
|
||||
}
|
||||
if saw_nl {
|
||||
pos = t;
|
||||
} else {
|
||||
res.extend_from_slice(&[b'\\', c]);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
res.extend_from_slice(&[b'\\', c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => res.push(c),
|
||||
}
|
||||
}
|
||||
res.as_bstr().to_string()
|
||||
}
|
||||
|
||||
fn parse_array(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let lo = self.next(true)?.span.lo;
|
||||
let mut entries = vec![];
|
||||
let mut consumed_comma = false;
|
||||
loop {
|
||||
if let Some(v) = self.lexer.peek(true) {
|
||||
if v.value == Token::RightBracket {
|
||||
let _ = self.next(true);
|
||||
let hi = v.span.hi;
|
||||
let span = Span { lo, hi };
|
||||
return Ok(Value::Array(entries).spanned(span));
|
||||
}
|
||||
if entries.len() > 0 && !mem::take(&mut consumed_comma) {
|
||||
self.error_handler.handle(
|
||||
ParserError::Expected("`,` or `]`", v.value.name(true)).spanned(v.span),
|
||||
);
|
||||
}
|
||||
}
|
||||
match self.parse_value() {
|
||||
Ok(v) => {
|
||||
entries.push(v);
|
||||
consumed_comma = self.skip_comma(true);
|
||||
}
|
||||
Err(e) => {
|
||||
self.skip_tree(Token::LeftBracket, Token::RightBracket);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inline_table(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let lo = self.next(true)?.span.lo;
|
||||
let mut map = IndexMap::new();
|
||||
let mut consumed_comma = false;
|
||||
loop {
|
||||
let token = match self.peek(false) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
self.error_handler.handle(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
if token.value == Token::RightBrace {
|
||||
let _ = self.next(false);
|
||||
break;
|
||||
}
|
||||
if !map.is_empty() && !mem::take(&mut consumed_comma) {
|
||||
self.error_handler.handle(
|
||||
ParserError::Expected("`,` or `}`", token.value.name(false))
|
||||
.spanned(token.span),
|
||||
);
|
||||
}
|
||||
let res = match self.parse_key_value_with_recovery() {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
self.skip_tree(Token::LeftBrace, Token::RightBrace);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
if let Some((mut key, value)) = res {
|
||||
self.insert(&mut map, &mut key, value, false, false);
|
||||
};
|
||||
consumed_comma = self.skip_comma(false);
|
||||
}
|
||||
let hi = self.last_span().hi;
|
||||
let span = Span { lo, hi };
|
||||
Ok(Value::Table(map).spanned(span))
|
||||
}
|
||||
|
||||
fn skip_comma(&mut self, value_context: bool) -> bool {
|
||||
if let Some(token) = self.lexer.peek(value_context) {
|
||||
if token.value != Token::Comma {
|
||||
return false;
|
||||
}
|
||||
let _ = self.next(value_context);
|
||||
}
|
||||
while let Some(token) = self.lexer.peek(value_context) {
|
||||
if token.value != Token::Comma {
|
||||
break;
|
||||
}
|
||||
let _ = self.next(value_context);
|
||||
self.error_handler
|
||||
.handle(ParserError::UnnecessaryComma.spanned(token.span));
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_document(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let mut map = IndexMap::new();
|
||||
self.parse_table_body(&mut map)?;
|
||||
while self.lexer.peek(false).is_some() {
|
||||
let (mut key, append) = self.parse_table_header()?;
|
||||
let mut inner_map = IndexMap::new();
|
||||
self.parse_table_body(&mut inner_map)?;
|
||||
let value = Value::Table(inner_map).spanned(key.span);
|
||||
self.insert(&mut map, &mut key.value, value, true, append);
|
||||
}
|
||||
let hi = self.last_span().hi;
|
||||
let span = Span { lo: 0, hi };
|
||||
Ok(Value::Table(map).spanned(span))
|
||||
}
|
||||
|
||||
fn parse_table_header(&mut self) -> Result<(Spanned<Key>, bool), Spanned<ParserError>> {
|
||||
let lo = self.next(false)?.span.lo;
|
||||
let mut append = false;
|
||||
if let Some(token) = self.lexer.peek(false) {
|
||||
if token.value == Token::LeftBracket {
|
||||
let _ = self.next(false);
|
||||
append = true;
|
||||
}
|
||||
}
|
||||
let key = self.parse_key()?;
|
||||
let mut hi = self.parse_exact(Token::RightBracket, false)?.hi;
|
||||
if append {
|
||||
hi = self.parse_exact(Token::RightBracket, false)?.hi;
|
||||
}
|
||||
let span = Span { lo, hi };
|
||||
Ok((key.spanned(span), append))
|
||||
}
|
||||
|
||||
fn parse_table_body(
|
||||
&mut self,
|
||||
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> Result<(), Spanned<ParserError>> {
|
||||
while let Some(e) = self.lexer.peek(false) {
|
||||
if e.value == Token::LeftBracket {
|
||||
return Ok(());
|
||||
}
|
||||
let Some((mut key, value)) = self.parse_key_value_with_recovery()? else {
|
||||
continue;
|
||||
};
|
||||
self.insert(dst, &mut key, value, false, false);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&self,
|
||||
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
keys: &mut Key,
|
||||
value: Spanned<Value>,
|
||||
modify_array_element: bool,
|
||||
append_last: bool,
|
||||
) {
|
||||
let key = keys.pop_front().unwrap();
|
||||
if keys.is_empty() {
|
||||
if let RawEntryMut::Occupied(mut old) =
|
||||
dst.raw_entry_mut_v1().from_key(key.value.as_str())
|
||||
{
|
||||
if append_last {
|
||||
if let Value::Array(array) = &mut old.get_mut().value {
|
||||
array.push(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Value::Table(old) = &mut old.get_mut().value {
|
||||
if let Value::Table(new) = value.value {
|
||||
for (k, v) in new {
|
||||
let mut keys = Key::new();
|
||||
keys.push_back(k);
|
||||
self.insert(old, &mut keys, v, false, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.error_handler
|
||||
.redefinition(ParserError::Redefined.spanned(key.span), old.key().span);
|
||||
old.shift_remove();
|
||||
}
|
||||
let span = value.span;
|
||||
let value = match append_last {
|
||||
true => Value::Array(vec![value]).spanned(span),
|
||||
false => value,
|
||||
};
|
||||
dst.insert(key, value);
|
||||
} else {
|
||||
if let RawEntryMut::Occupied(mut o) = dst.raw_entry_mut_v1().from_key(&key) {
|
||||
match &mut o.get_mut().value {
|
||||
Value::Table(dst) => {
|
||||
self.insert(dst, keys, value, modify_array_element, append_last);
|
||||
return;
|
||||
}
|
||||
Value::Array(array) if modify_array_element => {
|
||||
if let Some(Value::Table(dst)) =
|
||||
array.last_mut().as_mut().map(|v| &mut v.value)
|
||||
{
|
||||
self.insert(dst, keys, value, modify_array_element, append_last);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.error_handler
|
||||
.redefinition(ParserError::Redefined.spanned(key.span), o.key().span);
|
||||
o.shift_remove();
|
||||
}
|
||||
let mut map = IndexMap::new();
|
||||
let span = value.span;
|
||||
self.insert(&mut map, keys, value, modify_array_element, append_last);
|
||||
dst.insert(key, Value::Table(map).spanned(span));
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_key_value_with_recovery(
|
||||
&mut self,
|
||||
) -> Result<Option<(Key, Spanned<Value>)>, Spanned<ParserError>> {
|
||||
let pos = self.lexer.pos();
|
||||
match self.parse_key_value() {
|
||||
Ok(kv) => Ok(Some(kv)),
|
||||
Err((e, key)) => {
|
||||
if let Some(key) = key {
|
||||
let span = key.back().unwrap().span;
|
||||
self.error_handler
|
||||
.handle(ParserError::IgnoringKey.spanned(span));
|
||||
}
|
||||
if self.lexer.pos() == pos {
|
||||
Err(e)
|
||||
} else {
|
||||
self.error_handler.handle(e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn parse_key_value(
|
||||
&mut self,
|
||||
) -> Result<(Key, Spanned<Value>), (Spanned<ParserError>, Option<Key>)> {
|
||||
let key = self.parse_key();
|
||||
let eq = self.parse_exact(Token::Equals, true);
|
||||
let value = self.parse_value();
|
||||
let key = match key {
|
||||
Ok(k) => k,
|
||||
Err(e) => return Err((e, None)),
|
||||
};
|
||||
if let Err(e) = eq {
|
||||
return Err((e, Some(key)));
|
||||
}
|
||||
let value = match value {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err((e, Some(key))),
|
||||
};
|
||||
Ok((key, value))
|
||||
}
|
||||
|
||||
fn parse_key(&mut self) -> Result<Key, Spanned<ParserError>> {
|
||||
let mut parts = Key::new();
|
||||
loop {
|
||||
if parts.len() > 0 {
|
||||
if self.parse_exact(Token::Dot, false).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let Some(token) = self.lexer.peek(false) else {
|
||||
break;
|
||||
};
|
||||
let s = match token.value {
|
||||
Token::LiteralString(s) => s.as_bstr().to_string(),
|
||||
Token::CookedString(s) => self.cook_string(s),
|
||||
Token::Literal(l) => l.as_bstr().to_string(),
|
||||
_ => break,
|
||||
};
|
||||
parts.push_back(s.spanned(token.span));
|
||||
let _ = self.next(false);
|
||||
}
|
||||
if parts.is_empty() {
|
||||
Err(ParserError::MissingKey.spanned(self.next_span()))
|
||||
} else {
|
||||
Ok(parts)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_exact(
|
||||
&mut self,
|
||||
token: Token<'a>,
|
||||
value_context: bool,
|
||||
) -> Result<Span, Spanned<ParserError>> {
|
||||
let actual = match self.peek(value_context) {
|
||||
Ok(t) if t.value == token => {
|
||||
let _ = self.next(value_context);
|
||||
return Ok(t.span);
|
||||
}
|
||||
Ok(t) => t.value.name(value_context),
|
||||
Err(_) => "end of file",
|
||||
};
|
||||
let span = self.next_span();
|
||||
Err(ParserError::Expected(token.name(value_context), actual).spanned(span))
|
||||
}
|
||||
|
||||
fn last_span(&self) -> Span {
|
||||
self.last_span.unwrap_or(Span { lo: 0, hi: 0 })
|
||||
}
|
||||
|
||||
fn next_span(&mut self) -> Span {
|
||||
self.lexer.peek(false).map(|v| v.span).unwrap_or_else(|| {
|
||||
let hi = self.last_span().hi;
|
||||
Span { lo: hi, hi }
|
||||
})
|
||||
}
|
||||
|
||||
fn skip_tree(&mut self, start: Token, end: Token) {
|
||||
let mut depth = 1;
|
||||
while let Ok(next) = self.next(false) {
|
||||
if next.value == start {
|
||||
depth += 1;
|
||||
} else if next.value == end {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
toml-config/src/toml/toml_span.rs
Normal file
124
toml-config/src/toml/toml_span.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{Debug, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Span {
|
||||
pub lo: usize,
|
||||
pub hi: usize,
|
||||
}
|
||||
|
||||
impl Debug for Span {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}..{}", self.lo, self.hi)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq)]
|
||||
pub struct Spanned<T> {
|
||||
pub span: Span,
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
impl<T> Spanned<T> {
|
||||
pub fn as_ref(&self) -> Spanned<&T> {
|
||||
Spanned {
|
||||
span: self.span,
|
||||
value: &self.value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
|
||||
Spanned {
|
||||
span: self.span,
|
||||
value: f(self.value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into<U>(self) -> Spanned<U>
|
||||
where
|
||||
T: Into<U>,
|
||||
{
|
||||
Spanned {
|
||||
span: self.span,
|
||||
value: self.value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Spanned<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.value.fmt(f)?;
|
||||
write!(f, " @ {:?}", self.span)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Spanned<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value.eq(&other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Spanned<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.value.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for Spanned<String> {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpannedExt: Sized {
|
||||
fn spanned(self, span: Span) -> Spanned<Self>;
|
||||
}
|
||||
|
||||
impl<T> SpannedExt for T {
|
||||
fn spanned(self, span: Span) -> Spanned<Self> {
|
||||
Spanned { span, value: self }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DespanExt: Sized {
|
||||
type T;
|
||||
|
||||
fn despan(self) -> Option<Self::T>;
|
||||
fn despan_into<U>(self) -> Option<U>
|
||||
where
|
||||
Self::T: Into<U>;
|
||||
}
|
||||
|
||||
impl<T> DespanExt for Option<Spanned<T>> {
|
||||
type T = T;
|
||||
|
||||
fn despan(self) -> Option<Self::T> {
|
||||
self.map(|v| v.value)
|
||||
}
|
||||
|
||||
fn despan_into<U>(self) -> Option<U>
|
||||
where
|
||||
Self::T: Into<U>,
|
||||
{
|
||||
self.map(|v| v.value.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpannedResultExt1: Sized {
|
||||
type T;
|
||||
type E;
|
||||
|
||||
fn map_spanned<U, F: FnOnce(Self::T) -> U>(self, f: F) -> Result<Spanned<U>, Self::E>;
|
||||
}
|
||||
|
||||
impl<T, E> SpannedResultExt1 for Result<Spanned<T>, E> {
|
||||
type T = T;
|
||||
type E = E;
|
||||
|
||||
fn map_spanned<U, F: FnOnce(Self::T) -> U>(self, f: F) -> Result<Spanned<U>, Self::E> {
|
||||
self.map(|v| v.map(f))
|
||||
}
|
||||
}
|
||||
63
toml-config/src/toml/toml_value.rs
Normal file
63
toml-config/src/toml/toml_value.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use {
|
||||
crate::toml::toml_span::Spanned,
|
||||
indexmap::IndexMap,
|
||||
std::{
|
||||
cmp::Ordering,
|
||||
fmt::{Debug, Formatter},
|
||||
},
|
||||
};
|
||||
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Boolean(bool),
|
||||
Array(Vec<Spanned<Value>>),
|
||||
Table(IndexMap<Spanned<String>, Spanned<Value>>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Value::String(_) => "a string",
|
||||
Value::Integer(_) => "an integer",
|
||||
Value::Float(_) => "a float",
|
||||
Value::Boolean(_) => "a boolean",
|
||||
Value::Array(_) => "an array",
|
||||
Value::Table(_) => "a table",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Value::String(v) => v.fmt(f),
|
||||
Value::Integer(v) => v.fmt(f),
|
||||
Value::Float(v) => v.fmt(f),
|
||||
Value::Boolean(v) => v.fmt(f),
|
||||
Value::Array(v) => v.fmt(f),
|
||||
Value::Table(v) => v.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Value::String(v1), Value::String(v2)) => v1 == v2,
|
||||
(Value::Integer(v1), Value::Integer(v2)) => v1 == v2,
|
||||
(Value::Float(v1), Value::Float(v2)) => {
|
||||
if v1.is_nan() && v2.is_nan() {
|
||||
true
|
||||
} else {
|
||||
v1.total_cmp(v2) == Ordering::Equal
|
||||
}
|
||||
}
|
||||
(Value::Boolean(v1), Value::Boolean(v2)) => v1 == v2,
|
||||
(Value::Array(v1), Value::Array(v2)) => v1 == v2,
|
||||
(Value::Table(v1), Value::Table(v2)) => v1 == v2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
12
toml-spec/Cargo.toml
Normal file
12
toml-spec/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "toml-spec"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_yaml = "0.9.32"
|
||||
anyhow = "1.0.81"
|
||||
indexmap = { version = "2.2.5", features = ["serde"] }
|
||||
error_reporter = "1.0.0"
|
||||
serde_json = { version = "1.0.114", features = ["preserve_order"] }
|
||||
1100
toml-spec/spec/spec.generated.json
Normal file
1100
toml-spec/spec/spec.generated.json
Normal file
File diff suppressed because it is too large
Load diff
2302
toml-spec/spec/spec.generated.md
Normal file
2302
toml-spec/spec/spec.generated.md
Normal file
File diff suppressed because it is too large
Load diff
1936
toml-spec/spec/spec.yaml
Normal file
1936
toml-spec/spec/spec.yaml
Normal file
File diff suppressed because it is too large
Load diff
10
toml-spec/spec/template.md
Normal file
10
toml-spec/spec/template.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Jay TOML Config
|
||||
|
||||
This document describes the format of the TOML configuration of the Jay compositor.
|
||||
|
||||
A JSON Schema for this format is available at [spec.generated.json](./spec.generated.json). You can include this file in your editor to get auto completion.
|
||||
|
||||
Start at the top-level type: [Config](#types-config).
|
||||
|
||||
## Types
|
||||
|
||||
194
toml-spec/src/json_schema.rs
Normal file
194
toml-spec/src/json_schema.rs
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
use {
|
||||
crate::types::{
|
||||
ArraySpec, Described, MapSpec, NestableTypesSpec, NumberSpec, RefOrSpec, SingleTableSpec,
|
||||
StringSpec, TableSpec, TopLevelTypeSpec, VariantSpec,
|
||||
},
|
||||
anyhow::Result,
|
||||
serde_json::{json, Map, Value},
|
||||
};
|
||||
|
||||
pub fn generate_json_schema(
|
||||
types_sorted: &[(&String, &Described<TopLevelTypeSpec>)],
|
||||
) -> Result<()> {
|
||||
let mut types = Map::new();
|
||||
for (name, ty) in types_sorted {
|
||||
types.insert(name.to_string(), create_top_level_schema(ty));
|
||||
}
|
||||
|
||||
let json = json!({
|
||||
"$id": "jay_toml_schema",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$defs/Config",
|
||||
"$defs": types,
|
||||
});
|
||||
|
||||
let json = serde_json::to_string_pretty(&json).unwrap();
|
||||
|
||||
std::fs::write(
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.generated.json"),
|
||||
json.as_bytes(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_top_level_schema(spec: &Described<TopLevelTypeSpec>) -> Value {
|
||||
match &spec.value {
|
||||
TopLevelTypeSpec::Variable { variants } => {
|
||||
let mut cases = vec![];
|
||||
for variant in variants {
|
||||
cases.push(create_variant_schema(&variant.description, &variant.value));
|
||||
}
|
||||
json!({
|
||||
"description": spec.description,
|
||||
"anyOf": cases,
|
||||
})
|
||||
}
|
||||
TopLevelTypeSpec::Single(variant) => create_variant_schema(&spec.description, variant),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_variant_schema(description: &str, spec: &VariantSpec) -> Value {
|
||||
macro_rules! spec {
|
||||
($v:expr) => {
|
||||
match $v {
|
||||
RefOrSpec::Ref { name } => return create_ref_spec(description, name),
|
||||
RefOrSpec::Spec(s) => s,
|
||||
}
|
||||
};
|
||||
}
|
||||
match spec {
|
||||
VariantSpec::String(ss) => {
|
||||
let ss = spec!(ss);
|
||||
create_string_spec(description, ss)
|
||||
}
|
||||
VariantSpec::Number(ns) => {
|
||||
let ns = spec!(ns);
|
||||
create_number_spec(description, ns)
|
||||
}
|
||||
VariantSpec::Boolean => create_boolean_spec(description),
|
||||
VariantSpec::Array(s) => {
|
||||
let s = spec!(s);
|
||||
create_array_spec(description, s)
|
||||
}
|
||||
VariantSpec::Table(ts) => {
|
||||
let ts = spec!(ts);
|
||||
match ts {
|
||||
TableSpec::Tagged { types } => {
|
||||
let mut variants = vec![];
|
||||
for (name, ty) in types {
|
||||
variants.push(create_single_table_spec(
|
||||
&ty.description,
|
||||
&ty.value,
|
||||
Some(name),
|
||||
));
|
||||
}
|
||||
json!({
|
||||
"description": description,
|
||||
"anyOf": variants,
|
||||
})
|
||||
}
|
||||
TableSpec::Single(s) => create_single_table_spec(description, s, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_single_table_spec(
|
||||
description: &str,
|
||||
spec: &SingleTableSpec,
|
||||
type_: Option<&str>,
|
||||
) -> Value {
|
||||
let mut properties = Map::new();
|
||||
let mut required = vec![];
|
||||
if let Some(type_) = type_ {
|
||||
properties.insert("type".into(), json!({ "const": type_ }));
|
||||
required.push("type".into());
|
||||
}
|
||||
for (key, val) in &spec.fields {
|
||||
properties.insert(
|
||||
key.into(),
|
||||
create_nestable_type_spec(&val.description, &val.value.kind),
|
||||
);
|
||||
if val.value.required {
|
||||
required.push(key.to_string());
|
||||
}
|
||||
}
|
||||
json!({
|
||||
"description": description,
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_ref_spec(description: &str, name: &str) -> Value {
|
||||
let path = format!("#/$defs/{name}");
|
||||
json!({
|
||||
"description": description,
|
||||
"$ref": path,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_nestable_type_spec(description: &str, spec: &RefOrSpec<NestableTypesSpec>) -> Value {
|
||||
let spec = match spec {
|
||||
RefOrSpec::Ref { name } => return create_ref_spec(description, name),
|
||||
RefOrSpec::Spec(s) => s,
|
||||
};
|
||||
match spec {
|
||||
NestableTypesSpec::String(s) => create_string_spec(description, s),
|
||||
NestableTypesSpec::Number(s) => create_number_spec(description, s),
|
||||
NestableTypesSpec::Boolean => create_boolean_spec(description),
|
||||
NestableTypesSpec::Array(s) => create_array_spec(description, s),
|
||||
NestableTypesSpec::Map(s) => create_map_spec(description, s),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_map_spec(description: &str, spec: &MapSpec) -> Value {
|
||||
json!({
|
||||
"description": description,
|
||||
"type": "object",
|
||||
"additionalProperties": create_nestable_type_spec("", &spec.values),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_string_spec(description: &str, spec: &StringSpec) -> Value {
|
||||
let mut res = Map::new();
|
||||
res.insert("type".into(), json!("string"));
|
||||
res.insert("description".into(), json!(description));
|
||||
if let Some(values) = &spec.values {
|
||||
let strings: Vec<_> = values.iter().map(|v| &v.value.value).collect();
|
||||
res.insert("enum".into(), json!(strings));
|
||||
}
|
||||
res.into()
|
||||
}
|
||||
|
||||
fn create_array_spec(description: &str, spec: &ArraySpec) -> Value {
|
||||
json!({
|
||||
"type": "array",
|
||||
"description": description,
|
||||
"items": create_nestable_type_spec("", &spec.items),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_number_spec(description: &str, spec: &NumberSpec) -> Value {
|
||||
let ty = match spec.integer_only {
|
||||
true => "integer",
|
||||
false => "number",
|
||||
};
|
||||
let mut res = Map::new();
|
||||
res.insert("type".into(), json!(ty));
|
||||
res.insert("description".into(), json!(description));
|
||||
if let Some(minimum) = spec.minimum {
|
||||
let key = match spec.exclusive_minimum {
|
||||
true => "exclusiveMinimum",
|
||||
false => "minimum",
|
||||
};
|
||||
res.insert(key.into(), json!(minimum));
|
||||
}
|
||||
res.into()
|
||||
}
|
||||
|
||||
fn create_boolean_spec(description: &str) -> Value {
|
||||
json!({"type": "boolean", "description": description})
|
||||
}
|
||||
27
toml-spec/src/main.rs
Normal file
27
toml-spec/src/main.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use {
|
||||
crate::{
|
||||
json_schema::generate_json_schema,
|
||||
markdown::generate_markdown,
|
||||
types::{Described, TopLevelTypeSpec},
|
||||
},
|
||||
anyhow::Result,
|
||||
indexmap::IndexMap,
|
||||
};
|
||||
|
||||
mod json_schema;
|
||||
mod markdown;
|
||||
mod types;
|
||||
|
||||
fn parse() -> Result<IndexMap<String, Described<TopLevelTypeSpec>>> {
|
||||
let file = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.yaml"))?;
|
||||
Ok(serde_yaml::from_str(&file)?)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let types = parse()?;
|
||||
let mut types_sorted: Vec<_> = types.iter().collect();
|
||||
types_sorted.sort_by_key(|t| t.0);
|
||||
generate_markdown(&types_sorted)?;
|
||||
generate_json_schema(&types_sorted)?;
|
||||
Ok(())
|
||||
}
|
||||
259
toml-spec/src/markdown.rs
Normal file
259
toml-spec/src/markdown.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
use {
|
||||
crate::types::{
|
||||
ArraySpec, Described, NestableTypesSpec, NumberSpec, RefOrSpec, SingleTableSpec,
|
||||
StringSpec, TableSpec, TopLevelTypeSpec, VariantSpec,
|
||||
},
|
||||
anyhow::Result,
|
||||
std::io::Write,
|
||||
};
|
||||
|
||||
pub fn generate_markdown(types: &[(&String, &Described<TopLevelTypeSpec>)]) -> Result<()> {
|
||||
const TEMPLATE: &str = include_str!("../spec/template.md");
|
||||
|
||||
let mut buf = vec![];
|
||||
buf.extend_from_slice(TEMPLATE.as_bytes());
|
||||
|
||||
for (name, ty) in types {
|
||||
write_top_level_type_spec(&mut buf, name, ty)?;
|
||||
}
|
||||
|
||||
std::fs::write(
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.generated.md"),
|
||||
&buf,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_top_level_type_spec(
|
||||
buf: &mut Vec<u8>,
|
||||
name: &str,
|
||||
spec: &Described<TopLevelTypeSpec>,
|
||||
) -> Result<()> {
|
||||
writeln!(buf, "<a name=\"types-{name}\"></a>")?;
|
||||
writeln!(buf, "### `{name}`")?;
|
||||
writeln!(buf)?;
|
||||
writeln!(buf, "{}", spec.description.trim())?;
|
||||
writeln!(buf)?;
|
||||
match &spec.value {
|
||||
TopLevelTypeSpec::Variable { variants } => {
|
||||
writeln!(
|
||||
buf,
|
||||
"Values of this type should have one of the following forms:"
|
||||
)?;
|
||||
writeln!(buf)?;
|
||||
for variant in variants {
|
||||
write!(buf, "#### ")?;
|
||||
let name = match &variant.value {
|
||||
VariantSpec::String(_) => "A string",
|
||||
VariantSpec::Number(_) => "A number",
|
||||
VariantSpec::Boolean => "A boolean",
|
||||
VariantSpec::Array(_) => "An array",
|
||||
VariantSpec::Table(_) => "A table",
|
||||
};
|
||||
writeln!(buf, "{name}")?;
|
||||
writeln!(buf)?;
|
||||
writeln!(buf, "{}", variant.description.trim())?;
|
||||
writeln!(buf)?;
|
||||
write_variant_spec(buf, &variant.value)?;
|
||||
}
|
||||
}
|
||||
TopLevelTypeSpec::Single(variant) => {
|
||||
let name = match &variant {
|
||||
VariantSpec::String(_) => "strings",
|
||||
VariantSpec::Number(_) => "numbers",
|
||||
VariantSpec::Boolean => "booleans",
|
||||
VariantSpec::Array(_) => "arrays",
|
||||
VariantSpec::Table(_) => "tables",
|
||||
};
|
||||
writeln!(buf, "Values of this type should be {name}.")?;
|
||||
writeln!(buf)?;
|
||||
write_variant_spec(buf, variant)?;
|
||||
}
|
||||
}
|
||||
writeln!(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_variant_spec(buf: &mut Vec<u8>, spec: &VariantSpec) -> Result<()> {
|
||||
macro_rules! spec {
|
||||
($v:expr) => {
|
||||
match $v {
|
||||
RefOrSpec::Ref { name } => {
|
||||
writeln!(buf, "The value should be a [{name}](#types-{name}).")?;
|
||||
writeln!(buf)?;
|
||||
return Ok(());
|
||||
}
|
||||
RefOrSpec::Spec(s) => s,
|
||||
}
|
||||
};
|
||||
}
|
||||
match spec {
|
||||
VariantSpec::String(ss) => {
|
||||
let ss = spec!(ss);
|
||||
write_string_spec(buf, ss, "")?;
|
||||
}
|
||||
VariantSpec::Number(ns) => {
|
||||
let ns = spec!(ns);
|
||||
write_number_spec(buf, ns, "")?;
|
||||
}
|
||||
VariantSpec::Boolean => {}
|
||||
VariantSpec::Array(s) => {
|
||||
let s = spec!(s);
|
||||
write_array_spec(buf, s, "")?;
|
||||
}
|
||||
VariantSpec::Table(ts) => {
|
||||
let ts = spec!(ts);
|
||||
match ts {
|
||||
TableSpec::Tagged { types } => {
|
||||
writeln!(buf, "This table is a tagged union. The variant is determined by the `type` field. It takes one of the following values:")?;
|
||||
writeln!(buf)?;
|
||||
for (name, spec) in types {
|
||||
writeln!(buf, "- `{name}`:")?;
|
||||
writeln!(buf)?;
|
||||
for line in spec.description.trim().lines() {
|
||||
writeln!(buf, " {line}")?;
|
||||
}
|
||||
writeln!(buf)?;
|
||||
write_single_table_spec(buf, &spec.value, " ")?;
|
||||
}
|
||||
}
|
||||
TableSpec::Single(s) => {
|
||||
write_single_table_spec(buf, s, "")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_single_table_spec(buf: &mut Vec<u8>, spec: &SingleTableSpec, pad: &str) -> Result<()> {
|
||||
writeln!(buf, "{pad}The table has the following fields:")?;
|
||||
writeln!(buf)?;
|
||||
for (name, fs) in &spec.fields {
|
||||
let optional = match fs.value.required {
|
||||
true => "required",
|
||||
false => "optional",
|
||||
};
|
||||
writeln!(buf, "{pad}- `{name}` ({optional}):")?;
|
||||
writeln!(buf)?;
|
||||
for line in fs.description.trim().lines() {
|
||||
writeln!(buf, "{pad} {line}")?;
|
||||
}
|
||||
writeln!(buf)?;
|
||||
write!(buf, "{pad} The value of this field should be ")?;
|
||||
let spec = write_nestable_type_spec(buf, &fs.value.kind, false)?;
|
||||
writeln!(buf, ".")?;
|
||||
writeln!(buf)?;
|
||||
if let Some(spec) = spec {
|
||||
let pad = format!("{pad} ");
|
||||
write_nestable_type_restrictions(buf, spec, &pad)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_nestable_type_spec<'a>(
|
||||
buf: &mut Vec<u8>,
|
||||
spec: &'a RefOrSpec<NestableTypesSpec>,
|
||||
plural: bool,
|
||||
) -> Result<Option<&'a NestableTypesSpec>> {
|
||||
let spec = match spec {
|
||||
RefOrSpec::Ref { name } => {
|
||||
if plural {
|
||||
write!(buf, "[{name}s](#types-{name})")?;
|
||||
} else {
|
||||
write!(buf, "a [{name}](#types-{name})")?;
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
RefOrSpec::Spec(s) => s,
|
||||
};
|
||||
let name = match (spec, plural) {
|
||||
(NestableTypesSpec::String(_), false) => "a string",
|
||||
(NestableTypesSpec::String(_), true) => "strings",
|
||||
(NestableTypesSpec::Number(_), false) => "a number",
|
||||
(NestableTypesSpec::Number(_), true) => "numbers",
|
||||
(NestableTypesSpec::Boolean, false) => "a boolean",
|
||||
(NestableTypesSpec::Boolean, true) => "booleans",
|
||||
(NestableTypesSpec::Map(s), _) => {
|
||||
let name = match plural {
|
||||
true => "tables",
|
||||
false => "a table",
|
||||
};
|
||||
write!(buf, "{name} whose values are ")?;
|
||||
return write_nestable_type_spec(buf, &s.values, true);
|
||||
}
|
||||
(NestableTypesSpec::Array(s), _) => {
|
||||
let name = match plural {
|
||||
true => "arrays",
|
||||
false => "an array",
|
||||
};
|
||||
write!(buf, "{name} of ")?;
|
||||
return write_nestable_type_spec(buf, &s.items, true);
|
||||
}
|
||||
};
|
||||
write!(buf, "{name}")?;
|
||||
Ok(Some(spec))
|
||||
}
|
||||
|
||||
fn write_nestable_type_restrictions(
|
||||
buf: &mut Vec<u8>,
|
||||
spec: &NestableTypesSpec,
|
||||
pad: &str,
|
||||
) -> Result<()> {
|
||||
match spec {
|
||||
NestableTypesSpec::String(s) => write_string_spec(buf, s, pad),
|
||||
NestableTypesSpec::Number(s) => write_number_spec(buf, s, pad),
|
||||
NestableTypesSpec::Boolean => Ok(()),
|
||||
NestableTypesSpec::Array(_) => Ok(()),
|
||||
NestableTypesSpec::Map(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_string_spec(buf: &mut Vec<u8>, spec: &StringSpec, pad: &str) -> Result<()> {
|
||||
if let Some(values) = &spec.values {
|
||||
writeln!(
|
||||
buf,
|
||||
"{pad}The string should have one of the following values:"
|
||||
)?;
|
||||
writeln!(buf)?;
|
||||
for value in values {
|
||||
writeln!(buf, "{pad}- `{}`:", value.value.value)?;
|
||||
writeln!(buf)?;
|
||||
for line in value.description.lines() {
|
||||
writeln!(buf, "{pad} {line}")?;
|
||||
}
|
||||
writeln!(buf)?;
|
||||
}
|
||||
writeln!(buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_array_spec(buf: &mut Vec<u8>, spec: &ArraySpec, pad: &str) -> Result<()> {
|
||||
write!(buf, "{pad}Each element of this array should be ")?;
|
||||
let spec = write_nestable_type_spec(buf, &spec.items, false)?;
|
||||
writeln!(buf, ".")?;
|
||||
writeln!(buf)?;
|
||||
if let Some(spec) = spec {
|
||||
write_nestable_type_restrictions(buf, spec, pad)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_number_spec(buf: &mut Vec<u8>, spec: &NumberSpec, pad: &str) -> Result<()> {
|
||||
if spec.integer_only {
|
||||
writeln!(buf, "{pad}The numbers should be integers.")?;
|
||||
writeln!(buf)?;
|
||||
}
|
||||
if let Some(minimum) = spec.minimum {
|
||||
let greater = match spec.exclusive_minimum {
|
||||
true => "strictly greater than",
|
||||
false => "greater than or equal to",
|
||||
};
|
||||
writeln!(buf, "{pad}The numbers should be {greater} {minimum}.")?;
|
||||
writeln!(buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
184
toml-spec/src/types.rs
Normal file
184
toml-spec/src/types.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
use {
|
||||
error_reporter::Report,
|
||||
indexmap::IndexMap,
|
||||
serde::{
|
||||
de::{DeserializeOwned, Error},
|
||||
Deserialize, Deserializer,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Described<T> {
|
||||
pub description: String,
|
||||
#[serde(flatten)]
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TopLevelTypeSpec {
|
||||
Variable {
|
||||
variants: Vec<Described<VariantSpec>>,
|
||||
},
|
||||
Single(VariantSpec),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TableSpec {
|
||||
Tagged {
|
||||
types: IndexMap<String, Described<SingleTableSpec>>,
|
||||
},
|
||||
Single(SingleTableSpec),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SingleTableSpec {
|
||||
pub fields: IndexMap<String, Described<TableFieldSpec>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TableFieldSpec {
|
||||
pub required: bool,
|
||||
#[serde(flatten)]
|
||||
pub kind: RefOrSpec<NestableTypesSpec>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RefOrSpec<T> {
|
||||
Ref { name: String },
|
||||
Spec(T),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StringSpec {
|
||||
pub values: Option<Vec<Described<StringSpecValue>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StringSpecValue {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NumberSpec {
|
||||
#[serde(default)]
|
||||
pub integer_only: bool,
|
||||
pub minimum: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub exclusive_minimum: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", tag = "kind")]
|
||||
pub enum VariantSpec {
|
||||
String(RefOrSpec<StringSpec>),
|
||||
Number(RefOrSpec<NumberSpec>),
|
||||
Boolean,
|
||||
Array(RefOrSpec<ArraySpec>),
|
||||
Table(RefOrSpec<TableSpec>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ArraySpec {
|
||||
pub items: Box<RefOrSpec<NestableTypesSpec>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", tag = "kind")]
|
||||
pub enum NestableTypesSpec {
|
||||
String(StringSpec),
|
||||
Number(NumberSpec),
|
||||
Boolean,
|
||||
Array(ArraySpec),
|
||||
Map(MapSpec),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MapSpec {
|
||||
pub values: Box<RefOrSpec<NestableTypesSpec>>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TopLevelTypeSpec {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = serde_yaml::Value::deserialize(deserializer)?;
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Variable {
|
||||
variants: Vec<Described<VariantSpec>>,
|
||||
}
|
||||
let variable = Variable::deserialize(&v);
|
||||
let single = VariantSpec::deserialize(&v);
|
||||
let res = match (variable, single) {
|
||||
(Ok(variable), _) => Self::Variable {
|
||||
variants: variable.variants,
|
||||
},
|
||||
(_, Ok(single)) => Self::Single(single),
|
||||
(Err(e1), Err(e2)) => {
|
||||
return Err(Error::custom(format!(
|
||||
"spec must define either variants or a single variant. failures: {} ----- {}",
|
||||
Report::new(e1),
|
||||
Report::new(e2)
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TableSpec {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = serde_yaml::Value::deserialize(deserializer)?;
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Tagged {
|
||||
types: IndexMap<String, Described<SingleTableSpec>>,
|
||||
}
|
||||
let tagged = Tagged::deserialize(&v);
|
||||
let single = SingleTableSpec::deserialize(&v);
|
||||
let res = match (tagged, single) {
|
||||
(Ok(tagged), _) => Self::Tagged {
|
||||
types: tagged.types,
|
||||
},
|
||||
(_, Ok(single)) => Self::Single(single),
|
||||
(Err(e1), Err(e2)) => {
|
||||
return Err(Error::custom(format!(
|
||||
"spec must define either types or fields. failures: {} ----- {}",
|
||||
Report::new(e1),
|
||||
Report::new(e2)
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, U: DeserializeOwned> Deserialize<'de> for RefOrSpec<U> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = serde_yaml::Value::deserialize(deserializer)?;
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Ref {
|
||||
#[serde(rename = "ref")]
|
||||
name: String,
|
||||
}
|
||||
let name = Ref::deserialize(&v);
|
||||
let single = U::deserialize(&v);
|
||||
let res = match (name, single) {
|
||||
(Ok(name), _) => Self::Ref { name: name.name },
|
||||
(_, Ok(single)) => Self::Spec(single),
|
||||
(Err(e1), Err(e2)) => {
|
||||
return Err(Error::custom(format!(
|
||||
"spec must define either a ref or a spec. failures: {} ----- {}",
|
||||
Report::new(e1),
|
||||
Report::new(e2)
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue