diff --git a/.builds/unit-tests.yml b/.builds/unit-tests.yml index 140ecddb..2c962849 100644 --- a/.builds/unit-tests.yml +++ b/.builds/unit-tests.yml @@ -6,6 +6,7 @@ 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 + git submodule update --init - test: | cd jay cargo test diff --git a/.github/workflows/toml-spec.yml b/.github/workflows/toml-spec.yml new file mode 100644 index 00000000..3a82660d --- /dev/null +++ b/.github/workflows/toml-spec.yml @@ -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 diff --git a/.gitignore b/.gitignore index c9512ea7..b1256366 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .* !.gitignore +!.gitmodules !/.cargo !/.builds !/.github diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..90176cbd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "toml-config/toml-test"] + path = toml-config/toml-test + url = https://github.com/mahkoh/toml-tests.git diff --git a/Cargo.lock b/Cargo.lock index cf51603e..b4f48aee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", @@ -295,10 +295,28 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" name = "default-config" version = "0.1.0" dependencies = [ + "ahash", + "bstr", "chrono", + "error_reporter", + "indexmap", "jay-config", "log", + "phf", "rand", + "serde_json", + "simplelog", + "thiserror", + "walkdir", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", ] [[package]] @@ -479,12 +497,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]] @@ -627,9 +646,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 +665,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 +691,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 +744,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 +818,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 +976,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 +993,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 +1017,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 +1057,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 +1117,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 +1156,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 +1204,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 +1248,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 +1266,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 +1352,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" diff --git a/Cargo.toml b/Cargo.toml index 763b423b..dc13482b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } +default-config = { path = "toml-config" } algorithms = { path = "algorithms" } pin-project = "1.1.4" clap = { version = "4.4.18", features = ["derive", "wrap_help"] } diff --git a/default-config/Cargo.toml b/default-config/Cargo.toml deleted file mode 100644 index 77fb8f22..00000000 --- a/default-config/Cargo.toml +++ /dev/null @@ -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" diff --git a/default-config/src/lib.rs b/default-config/src/lib.rs deleted file mode 100644 index 3a1f7a55..00000000 --- a/default-config/src/lib.rs +++ /dev/null @@ -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); diff --git a/toml-config/Cargo.toml b/toml-config/Cargo.toml new file mode 100644 index 00000000..28b82abd --- /dev/null +++ b/toml-config/Cargo.toml @@ -0,0 +1,24 @@ +[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" +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" diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs new file mode 100644 index 00000000..435fecfd --- /dev/null +++ b/toml-config/src/config.rs @@ -0,0 +1,310 @@ +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}, + }, + std::{ + error::Error, + fmt::{Display, Formatter}, + }, + thiserror::Error, + toml::toml_parser, +}; + +#[derive(Debug, Copy, Clone)] +pub enum SimpleCommand { + Close, + DisablePointerConstraint, + FocusDown, + FocusLeft, + FocusParent, + FocusRight, + FocusUp, + MoveDown, + MoveLeft, + MoveRight, + MoveUp, + None, + Quit, + ReloadConfigSo, + ReloadConfigToml, + SplitHorizontal, + SplitVertical, + ToggleFloating, + ToggleFullscreen, + ToggleMono, + ToggleSplit, +} + +#[derive(Debug, Clone)] +pub enum Action { + ConfigureConnector { con: ConfigConnector }, + ConfigureDirectScanout { enabled: bool }, + ConfigureDrmDevice { dev: ConfigDrmDevice }, + ConfigureInput { input: Input }, + ConfigureOutput { out: Output }, + Exec { exec: Exec }, + Multi { actions: Vec }, + SetEnv { env: Vec<(String, String)> }, + SetGfxApi { api: GfxApi }, + SetKeymap { map: ConfigKeymap }, + SetLogLevel { level: LogLevel }, + SetRenderDevice { dev: DrmDeviceMatch }, + SetStatus { status: Option }, + SetTheme { theme: Box }, + ShowWorkspace { name: String }, + SimpleCommand { cmd: SimpleCommand }, + SwitchToVt { num: u32 }, + UnsetEnv { env: Vec }, +} + +#[derive(Debug, Clone, Default)] +pub struct Theme { + pub attention_requested_bg_color: Option, + pub bg_color: Option, + pub bar_bg_color: Option, + pub bar_status_text_color: Option, + pub border_color: Option, + pub captured_focused_title_bg_color: Option, + pub captured_unfocused_title_bg_color: Option, + pub focused_inactive_title_bg_color: Option, + pub focused_inactive_title_text_color: Option, + pub focused_title_bg_color: Option, + pub focused_title_text_color: Option, + pub separator_color: Option, + pub unfocused_title_bg_color: Option, + pub unfocused_title_text_color: Option, + pub border_width: Option, + pub title_height: Option, + pub font: Option, +} + +#[derive(Debug, Clone)] +pub struct Status { + pub format: MessageFormat, + pub exec: Exec, + pub separator: Option, +} + +#[derive(Debug, Clone)] +pub enum OutputMatch { + Any(Vec), + All { + name: Option, + connector: Option, + serial_number: Option, + manufacturer: Option, + model: Option, + }, +} + +#[derive(Debug, Clone)] +pub enum DrmDeviceMatch { + Any(Vec), + All { + name: Option, + syspath: Option, + vendor: Option, + vendor_name: Option, + model: Option, + model_name: Option, + devnode: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct Mode { + pub width: i32, + pub height: i32, + pub refresh_rate: Option, +} + +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, + pub match_: OutputMatch, + pub x: Option, + pub y: Option, + pub scale: Option, + pub transform: Option, + pub mode: Option, +} + +#[derive(Debug, Clone)] +pub enum ConnectorMatch { + Any(Vec), + All { connector: Option }, +} + +#[derive(Debug, Clone)] +pub enum InputMatch { + Any(Vec), + All { + tag: Option, + name: Option, + syspath: Option, + devnode: Option, + is_keyboard: Option, + is_pointer: Option, + is_touch: Option, + is_tablet_tool: Option, + is_tablet_pad: Option, + is_gesture: Option, + is_switch: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct Input { + pub tag: Option, + pub match_: InputMatch, + pub accel_profile: Option, + pub accel_speed: Option, + pub tap_enabled: Option, + pub tap_drag_enabled: Option, + pub tap_drag_lock_enabled: Option, + pub left_handed: Option, + pub natural_scrolling: Option, + pub px_per_wheel_scroll: Option, + pub transform_matrix: Option<[[f64; 2]; 2]>, +} + +#[derive(Debug, Clone)] +pub struct Exec { + pub prog: String, + pub args: Vec, + 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, + pub match_: DrmDeviceMatch, + pub gfx_api: Option, + pub direct_scanout_enabled: Option, +} + +#[derive(Debug, Clone)] +pub enum ConfigKeymap { + Named(String), + Literal(Keymap), + Defined { name: String, map: Keymap }, +} + +#[derive(Debug, Clone)] +pub struct Config { + pub keymap: Option, + pub shortcuts: Vec<(ModifiedKeySym, Action)>, + pub on_graphics_initialized: Option, + pub on_idle: Option, + pub status: Option, + pub connectors: Vec, + pub outputs: Vec, + pub workspace_capture: bool, + pub env: Vec<(String, String)>, + pub on_startup: Option, + pub keymaps: Vec, + pub log_level: Option, + pub theme: Theme, + pub gfx_api: Option, + pub direct_scanout_enabled: Option, + pub drm_devices: Vec, + pub render_device: Option, + pub inputs: Vec, +} + +#[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(input: &[u8], handle_error: F) -> Option +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(); +} diff --git a/toml-config/src/config/context.rs b/toml-config/src/config/context.rs new file mode 100644 index 00000000..704d36b3 --- /dev/null +++ b/toml-config/src/config/context.rs @@ -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, +} + +#[derive(Default)] +pub struct Used { + pub outputs: Vec>, + pub inputs: Vec>, + pub drm_devices: Vec>, + pub keymaps: Vec>, + pub defined_outputs: AHashSet>, + pub defined_inputs: AHashSet>, + pub defined_drm_devices: AHashSet>, + pub defined_keymaps: AHashSet>, +} + +impl<'a> Context<'a> { + pub fn error(&self, cause: Spanned) -> SpannedError<'a, E> { + self.error2(cause.span, cause.value) + } + + pub fn error2(&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) { + log::warn!("{}", Report::new(self.error(err))); + } + + fn redefinition(&self, err: Spanned, prev: Span) { + log::warn!("{}", Report::new(self.error(err))); + log::info!( + "Previous definition here: {}", + Report::new(self.error3(prev)) + ); + } +} diff --git a/toml-config/src/config/error.rs b/toml-config/src/config/error.rs new file mode 100644 index 00000000..b2679300 --- /dev/null +++ b/toml-config/src/config/error.rs @@ -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, +} + +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) +} diff --git a/toml-config/src/config/extractor.rs b/toml-config/src/config/extractor.rs new file mode 100644 index 00000000..d4f8a61d --- /dev/null +++ b/toml-config/src/config/extractor.rs @@ -0,0 +1,278 @@ +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>, + 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>, + ) -> Self { + Self { + cx, + table, + used: Default::default(), + log_unused: true, + span, + } + } + + fn get(&mut self, name: &'static str) -> Option<&'v Spanned> { + 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, U>(&mut self, e: E) -> Result> + where + ExtractorError: Into, + { + e.extract(self).map_err(|e| e.map(|e| e.into())) + } + + pub fn extract_or_ignore, U>( + &mut self, + e: E, + ) -> Result> + where + ExtractorError: Into, + { + 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 i32")] + I32, +} + +pub trait Extractable<'v> { + type Output; + + fn extract( + self, + extractor: &mut Extractor<'v>, + ) -> Result>; +} + +impl<'v, T, F> Extractable<'v> for F +where + F: FnOnce(&mut Extractor<'v>) -> Result>, +{ + type Output = T; + + fn extract( + self, + extractor: &mut Extractor<'v>, + ) -> Result> { + self(extractor) + } +} + +pub fn val( + name: &'static str, +) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result, Spanned> +{ + 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> { + 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], v, &**v, "an array"); +ty!(tbl, 'a, Table, &'a IndexMap, Spanned>, v, v, "a table"); + +pub fn fltorint( + name: &'static str, +) -> impl for<'a> FnOnce(&mut Extractor<'a>) -> Result, Spanned> { + 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> { + 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!(s32, i32, I32); + +pub fn recover(f: F) -> Recover { + Recover(f) +} + +pub struct Recover(E); + +impl<'v, E> Extractable<'v> for Recover +where + E: Extractable<'v>, + >::Output: Default, +{ + type Output = >::Output; + + fn extract( + self, + extractor: &mut Extractor<'v>, + ) -> Result> { + match self.0.extract(extractor) { + Ok(v) => Ok(v), + Err(e) => { + log::warn!("{}", extractor.cx.error(e)); + Ok(>::Output::default()) + } + } + } +} + +pub fn opt(f: F) -> Opt { + Opt(f) +} + +pub struct Opt(E); + +impl<'v, E> Extractable<'v> for Opt +where + E: Extractable<'v>, +{ + type Output = Option<>::Output>; + + fn extract( + self, + extractor: &mut Extractor<'v>, + ) -> Result> { + 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> { + $( + 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,); diff --git a/toml-config/src/config/keysyms.rs b/toml-config/src/config/keysyms.rs new file mode 100644 index 00000000..50f4d409 --- /dev/null +++ b/toml-config/src/config/keysyms.rs @@ -0,0 +1,2580 @@ +use {jay_config::keyboard::syms::*, phf::phf_map}; + +pub static KEYSYMS: phf::Map<&'static str, KeySym> = phf_map! { + "NoSymbol" => SYM_NoSymbol, + "VoidSymbol" => SYM_VoidSymbol, + "BackSpace" => SYM_BackSpace, + "Tab" => SYM_Tab, + "Linefeed" => SYM_Linefeed, + "Clear" => SYM_Clear, + "Return" => SYM_Return, + "Pause" => SYM_Pause, + "Scroll_Lock" => SYM_Scroll_Lock, + "Sys_Req" => SYM_Sys_Req, + "Escape" => SYM_Escape, + "Delete" => SYM_Delete, + "Multi_key" => SYM_Multi_key, + "Codeinput" => SYM_Codeinput, + "SingleCandidate" => SYM_SingleCandidate, + "MultipleCandidate" => SYM_MultipleCandidate, + "PreviousCandidate" => SYM_PreviousCandidate, + "Kanji" => SYM_Kanji, + "Muhenkan" => SYM_Muhenkan, + "Henkan_Mode" => SYM_Henkan_Mode, + "Henkan" => SYM_Henkan, + "Romaji" => SYM_Romaji, + "Hiragana" => SYM_Hiragana, + "Katakana" => SYM_Katakana, + "Hiragana_Katakana" => SYM_Hiragana_Katakana, + "Zenkaku" => SYM_Zenkaku, + "Hankaku" => SYM_Hankaku, + "Zenkaku_Hankaku" => SYM_Zenkaku_Hankaku, + "Touroku" => SYM_Touroku, + "Massyo" => SYM_Massyo, + "Kana_Lock" => SYM_Kana_Lock, + "Kana_Shift" => SYM_Kana_Shift, + "Eisu_Shift" => SYM_Eisu_Shift, + "Eisu_toggle" => SYM_Eisu_toggle, + "Kanji_Bangou" => SYM_Kanji_Bangou, + "Zen_Koho" => SYM_Zen_Koho, + "Mae_Koho" => SYM_Mae_Koho, + "Home" => SYM_Home, + "Left" => SYM_Left, + "Up" => SYM_Up, + "Right" => SYM_Right, + "Down" => SYM_Down, + "Prior" => SYM_Prior, + "Page_Up" => SYM_Page_Up, + "Next" => SYM_Next, + "Page_Down" => SYM_Page_Down, + "End" => SYM_End, + "Begin" => SYM_Begin, + "Select" => SYM_Select, + "Print" => SYM_Print, + "Execute" => SYM_Execute, + "Insert" => SYM_Insert, + "Undo" => SYM_Undo, + "Redo" => SYM_Redo, + "Menu" => SYM_Menu, + "Find" => SYM_Find, + "Cancel" => SYM_Cancel, + "Help" => SYM_Help, + "Break" => SYM_Break, + "Mode_switch" => SYM_Mode_switch, + "script_switch" => SYM_script_switch, + "Num_Lock" => SYM_Num_Lock, + "KP_Space" => SYM_KP_Space, + "KP_Tab" => SYM_KP_Tab, + "KP_Enter" => SYM_KP_Enter, + "KP_F1" => SYM_KP_F1, + "KP_F2" => SYM_KP_F2, + "KP_F3" => SYM_KP_F3, + "KP_F4" => SYM_KP_F4, + "KP_Home" => SYM_KP_Home, + "KP_Left" => SYM_KP_Left, + "KP_Up" => SYM_KP_Up, + "KP_Right" => SYM_KP_Right, + "KP_Down" => SYM_KP_Down, + "KP_Prior" => SYM_KP_Prior, + "KP_Page_Up" => SYM_KP_Page_Up, + "KP_Next" => SYM_KP_Next, + "KP_Page_Down" => SYM_KP_Page_Down, + "KP_End" => SYM_KP_End, + "KP_Begin" => SYM_KP_Begin, + "KP_Insert" => SYM_KP_Insert, + "KP_Delete" => SYM_KP_Delete, + "KP_Equal" => SYM_KP_Equal, + "KP_Multiply" => SYM_KP_Multiply, + "KP_Add" => SYM_KP_Add, + "KP_Separator" => SYM_KP_Separator, + "KP_Subtract" => SYM_KP_Subtract, + "KP_Decimal" => SYM_KP_Decimal, + "KP_Divide" => SYM_KP_Divide, + "KP_0" => SYM_KP_0, + "KP_1" => SYM_KP_1, + "KP_2" => SYM_KP_2, + "KP_3" => SYM_KP_3, + "KP_4" => SYM_KP_4, + "KP_5" => SYM_KP_5, + "KP_6" => SYM_KP_6, + "KP_7" => SYM_KP_7, + "KP_8" => SYM_KP_8, + "KP_9" => SYM_KP_9, + "F1" => SYM_F1, + "F2" => SYM_F2, + "F3" => SYM_F3, + "F4" => SYM_F4, + "F5" => SYM_F5, + "F6" => SYM_F6, + "F7" => SYM_F7, + "F8" => SYM_F8, + "F9" => SYM_F9, + "F10" => SYM_F10, + "F11" => SYM_F11, + "L1" => SYM_L1, + "F12" => SYM_F12, + "L2" => SYM_L2, + "F13" => SYM_F13, + "L3" => SYM_L3, + "F14" => SYM_F14, + "L4" => SYM_L4, + "F15" => SYM_F15, + "L5" => SYM_L5, + "F16" => SYM_F16, + "L6" => SYM_L6, + "F17" => SYM_F17, + "L7" => SYM_L7, + "F18" => SYM_F18, + "L8" => SYM_L8, + "F19" => SYM_F19, + "L9" => SYM_L9, + "F20" => SYM_F20, + "L10" => SYM_L10, + "F21" => SYM_F21, + "R1" => SYM_R1, + "F22" => SYM_F22, + "R2" => SYM_R2, + "F23" => SYM_F23, + "R3" => SYM_R3, + "F24" => SYM_F24, + "R4" => SYM_R4, + "F25" => SYM_F25, + "R5" => SYM_R5, + "F26" => SYM_F26, + "R6" => SYM_R6, + "F27" => SYM_F27, + "R7" => SYM_R7, + "F28" => SYM_F28, + "R8" => SYM_R8, + "F29" => SYM_F29, + "R9" => SYM_R9, + "F30" => SYM_F30, + "R10" => SYM_R10, + "F31" => SYM_F31, + "R11" => SYM_R11, + "F32" => SYM_F32, + "R12" => SYM_R12, + "F33" => SYM_F33, + "R13" => SYM_R13, + "F34" => SYM_F34, + "R14" => SYM_R14, + "F35" => SYM_F35, + "R15" => SYM_R15, + "Shift_L" => SYM_Shift_L, + "Shift_R" => SYM_Shift_R, + "Control_L" => SYM_Control_L, + "Control_R" => SYM_Control_R, + "Caps_Lock" => SYM_Caps_Lock, + "Shift_Lock" => SYM_Shift_Lock, + "Meta_L" => SYM_Meta_L, + "Meta_R" => SYM_Meta_R, + "Alt_L" => SYM_Alt_L, + "Alt_R" => SYM_Alt_R, + "Super_L" => SYM_Super_L, + "Super_R" => SYM_Super_R, + "Hyper_L" => SYM_Hyper_L, + "Hyper_R" => SYM_Hyper_R, + "ISO_Lock" => SYM_ISO_Lock, + "ISO_Level2_Latch" => SYM_ISO_Level2_Latch, + "ISO_Level3_Shift" => SYM_ISO_Level3_Shift, + "ISO_Level3_Latch" => SYM_ISO_Level3_Latch, + "ISO_Level3_Lock" => SYM_ISO_Level3_Lock, + "ISO_Level5_Shift" => SYM_ISO_Level5_Shift, + "ISO_Level5_Latch" => SYM_ISO_Level5_Latch, + "ISO_Level5_Lock" => SYM_ISO_Level5_Lock, + "ISO_Group_Shift" => SYM_ISO_Group_Shift, + "ISO_Group_Latch" => SYM_ISO_Group_Latch, + "ISO_Group_Lock" => SYM_ISO_Group_Lock, + "ISO_Next_Group" => SYM_ISO_Next_Group, + "ISO_Next_Group_Lock" => SYM_ISO_Next_Group_Lock, + "ISO_Prev_Group" => SYM_ISO_Prev_Group, + "ISO_Prev_Group_Lock" => SYM_ISO_Prev_Group_Lock, + "ISO_First_Group" => SYM_ISO_First_Group, + "ISO_First_Group_Lock" => SYM_ISO_First_Group_Lock, + "ISO_Last_Group" => SYM_ISO_Last_Group, + "ISO_Last_Group_Lock" => SYM_ISO_Last_Group_Lock, + "ISO_Left_Tab" => SYM_ISO_Left_Tab, + "ISO_Move_Line_Up" => SYM_ISO_Move_Line_Up, + "ISO_Move_Line_Down" => SYM_ISO_Move_Line_Down, + "ISO_Partial_Line_Up" => SYM_ISO_Partial_Line_Up, + "ISO_Partial_Line_Down" => SYM_ISO_Partial_Line_Down, + "ISO_Partial_Space_Left" => SYM_ISO_Partial_Space_Left, + "ISO_Partial_Space_Right" => SYM_ISO_Partial_Space_Right, + "ISO_Set_Margin_Left" => SYM_ISO_Set_Margin_Left, + "ISO_Set_Margin_Right" => SYM_ISO_Set_Margin_Right, + "ISO_Release_Margin_Left" => SYM_ISO_Release_Margin_Left, + "ISO_Release_Margin_Right" => SYM_ISO_Release_Margin_Right, + "ISO_Release_Both_Margins" => SYM_ISO_Release_Both_Margins, + "ISO_Fast_Cursor_Left" => SYM_ISO_Fast_Cursor_Left, + "ISO_Fast_Cursor_Right" => SYM_ISO_Fast_Cursor_Right, + "ISO_Fast_Cursor_Up" => SYM_ISO_Fast_Cursor_Up, + "ISO_Fast_Cursor_Down" => SYM_ISO_Fast_Cursor_Down, + "ISO_Continuous_Underline" => SYM_ISO_Continuous_Underline, + "ISO_Discontinuous_Underline" => SYM_ISO_Discontinuous_Underline, + "ISO_Emphasize" => SYM_ISO_Emphasize, + "ISO_Center_Object" => SYM_ISO_Center_Object, + "ISO_Enter" => SYM_ISO_Enter, + "dead_grave" => SYM_dead_grave, + "dead_acute" => SYM_dead_acute, + "dead_circumflex" => SYM_dead_circumflex, + "dead_tilde" => SYM_dead_tilde, + "dead_perispomeni" => SYM_dead_perispomeni, + "dead_macron" => SYM_dead_macron, + "dead_breve" => SYM_dead_breve, + "dead_abovedot" => SYM_dead_abovedot, + "dead_diaeresis" => SYM_dead_diaeresis, + "dead_abovering" => SYM_dead_abovering, + "dead_doubleacute" => SYM_dead_doubleacute, + "dead_caron" => SYM_dead_caron, + "dead_cedilla" => SYM_dead_cedilla, + "dead_ogonek" => SYM_dead_ogonek, + "dead_iota" => SYM_dead_iota, + "dead_voiced_sound" => SYM_dead_voiced_sound, + "dead_semivoiced_sound" => SYM_dead_semivoiced_sound, + "dead_belowdot" => SYM_dead_belowdot, + "dead_hook" => SYM_dead_hook, + "dead_horn" => SYM_dead_horn, + "dead_stroke" => SYM_dead_stroke, + "dead_abovecomma" => SYM_dead_abovecomma, + "dead_psili" => SYM_dead_psili, + "dead_abovereversedcomma" => SYM_dead_abovereversedcomma, + "dead_dasia" => SYM_dead_dasia, + "dead_doublegrave" => SYM_dead_doublegrave, + "dead_belowring" => SYM_dead_belowring, + "dead_belowmacron" => SYM_dead_belowmacron, + "dead_belowcircumflex" => SYM_dead_belowcircumflex, + "dead_belowtilde" => SYM_dead_belowtilde, + "dead_belowbreve" => SYM_dead_belowbreve, + "dead_belowdiaeresis" => SYM_dead_belowdiaeresis, + "dead_invertedbreve" => SYM_dead_invertedbreve, + "dead_belowcomma" => SYM_dead_belowcomma, + "dead_currency" => SYM_dead_currency, + "dead_lowline" => SYM_dead_lowline, + "dead_aboveverticalline" => SYM_dead_aboveverticalline, + "dead_belowverticalline" => SYM_dead_belowverticalline, + "dead_longsolidusoverlay" => SYM_dead_longsolidusoverlay, + "dead_a" => SYM_dead_a, + "dead_A" => SYM_dead_A, + "dead_e" => SYM_dead_e, + "dead_E" => SYM_dead_E, + "dead_i" => SYM_dead_i, + "dead_I" => SYM_dead_I, + "dead_o" => SYM_dead_o, + "dead_O" => SYM_dead_O, + "dead_u" => SYM_dead_u, + "dead_U" => SYM_dead_U, + "dead_small_schwa" => SYM_dead_small_schwa, + "dead_schwa" => SYM_dead_schwa, + "dead_capital_schwa" => SYM_dead_capital_schwa, + "dead_SCHWA" => SYM_dead_SCHWA, + "dead_greek" => SYM_dead_greek, + "dead_hamza" => SYM_dead_hamza, + "First_Virtual_Screen" => SYM_First_Virtual_Screen, + "Prev_Virtual_Screen" => SYM_Prev_Virtual_Screen, + "Next_Virtual_Screen" => SYM_Next_Virtual_Screen, + "Last_Virtual_Screen" => SYM_Last_Virtual_Screen, + "Terminate_Server" => SYM_Terminate_Server, + "AccessX_Enable" => SYM_AccessX_Enable, + "AccessX_Feedback_Enable" => SYM_AccessX_Feedback_Enable, + "RepeatKeys_Enable" => SYM_RepeatKeys_Enable, + "SlowKeys_Enable" => SYM_SlowKeys_Enable, + "BounceKeys_Enable" => SYM_BounceKeys_Enable, + "StickyKeys_Enable" => SYM_StickyKeys_Enable, + "MouseKeys_Enable" => SYM_MouseKeys_Enable, + "MouseKeys_Accel_Enable" => SYM_MouseKeys_Accel_Enable, + "Overlay1_Enable" => SYM_Overlay1_Enable, + "Overlay2_Enable" => SYM_Overlay2_Enable, + "AudibleBell_Enable" => SYM_AudibleBell_Enable, + "Pointer_Left" => SYM_Pointer_Left, + "Pointer_Right" => SYM_Pointer_Right, + "Pointer_Up" => SYM_Pointer_Up, + "Pointer_Down" => SYM_Pointer_Down, + "Pointer_UpLeft" => SYM_Pointer_UpLeft, + "Pointer_UpRight" => SYM_Pointer_UpRight, + "Pointer_DownLeft" => SYM_Pointer_DownLeft, + "Pointer_DownRight" => SYM_Pointer_DownRight, + "Pointer_Button_Dflt" => SYM_Pointer_Button_Dflt, + "Pointer_Button1" => SYM_Pointer_Button1, + "Pointer_Button2" => SYM_Pointer_Button2, + "Pointer_Button3" => SYM_Pointer_Button3, + "Pointer_Button4" => SYM_Pointer_Button4, + "Pointer_Button5" => SYM_Pointer_Button5, + "Pointer_DblClick_Dflt" => SYM_Pointer_DblClick_Dflt, + "Pointer_DblClick1" => SYM_Pointer_DblClick1, + "Pointer_DblClick2" => SYM_Pointer_DblClick2, + "Pointer_DblClick3" => SYM_Pointer_DblClick3, + "Pointer_DblClick4" => SYM_Pointer_DblClick4, + "Pointer_DblClick5" => SYM_Pointer_DblClick5, + "Pointer_Drag_Dflt" => SYM_Pointer_Drag_Dflt, + "Pointer_Drag1" => SYM_Pointer_Drag1, + "Pointer_Drag2" => SYM_Pointer_Drag2, + "Pointer_Drag3" => SYM_Pointer_Drag3, + "Pointer_Drag4" => SYM_Pointer_Drag4, + "Pointer_Drag5" => SYM_Pointer_Drag5, + "Pointer_EnableKeys" => SYM_Pointer_EnableKeys, + "Pointer_Accelerate" => SYM_Pointer_Accelerate, + "Pointer_DfltBtnNext" => SYM_Pointer_DfltBtnNext, + "Pointer_DfltBtnPrev" => SYM_Pointer_DfltBtnPrev, + "ch" => SYM_ch, + "Ch" => SYM_Ch, + "CH" => SYM_CH, + "c_h" => SYM_c_h, + "C_h" => SYM_C_h, + "C_H" => SYM_C_H, + "3270_Duplicate" => SYM_3270_Duplicate, + "3270_FieldMark" => SYM_3270_FieldMark, + "3270_Right2" => SYM_3270_Right2, + "3270_Left2" => SYM_3270_Left2, + "3270_BackTab" => SYM_3270_BackTab, + "3270_EraseEOF" => SYM_3270_EraseEOF, + "3270_EraseInput" => SYM_3270_EraseInput, + "3270_Reset" => SYM_3270_Reset, + "3270_Quit" => SYM_3270_Quit, + "3270_PA1" => SYM_3270_PA1, + "3270_PA2" => SYM_3270_PA2, + "3270_PA3" => SYM_3270_PA3, + "3270_Test" => SYM_3270_Test, + "3270_Attn" => SYM_3270_Attn, + "3270_CursorBlink" => SYM_3270_CursorBlink, + "3270_AltCursor" => SYM_3270_AltCursor, + "3270_KeyClick" => SYM_3270_KeyClick, + "3270_Jump" => SYM_3270_Jump, + "3270_Ident" => SYM_3270_Ident, + "3270_Rule" => SYM_3270_Rule, + "3270_Copy" => SYM_3270_Copy, + "3270_Play" => SYM_3270_Play, + "3270_Setup" => SYM_3270_Setup, + "3270_Record" => SYM_3270_Record, + "3270_ChangeScreen" => SYM_3270_ChangeScreen, + "3270_DeleteWord" => SYM_3270_DeleteWord, + "3270_ExSelect" => SYM_3270_ExSelect, + "3270_CursorSelect" => SYM_3270_CursorSelect, + "3270_PrintScreen" => SYM_3270_PrintScreen, + "3270_Enter" => SYM_3270_Enter, + "space" => SYM_space, + "exclam" => SYM_exclam, + "quotedbl" => SYM_quotedbl, + "numbersign" => SYM_numbersign, + "dollar" => SYM_dollar, + "percent" => SYM_percent, + "ampersand" => SYM_ampersand, + "apostrophe" => SYM_apostrophe, + "quoteright" => SYM_quoteright, + "parenleft" => SYM_parenleft, + "parenright" => SYM_parenright, + "asterisk" => SYM_asterisk, + "plus" => SYM_plus, + "comma" => SYM_comma, + "minus" => SYM_minus, + "period" => SYM_period, + "slash" => SYM_slash, + "0" => SYM_0, + "1" => SYM_1, + "2" => SYM_2, + "3" => SYM_3, + "4" => SYM_4, + "5" => SYM_5, + "6" => SYM_6, + "7" => SYM_7, + "8" => SYM_8, + "9" => SYM_9, + "colon" => SYM_colon, + "semicolon" => SYM_semicolon, + "less" => SYM_less, + "equal" => SYM_equal, + "greater" => SYM_greater, + "question" => SYM_question, + "at" => SYM_at, + "A" => SYM_A, + "B" => SYM_B, + "C" => SYM_C, + "D" => SYM_D, + "E" => SYM_E, + "F" => SYM_F, + "G" => SYM_G, + "H" => SYM_H, + "I" => SYM_I, + "J" => SYM_J, + "K" => SYM_K, + "L" => SYM_L, + "M" => SYM_M, + "N" => SYM_N, + "O" => SYM_O, + "P" => SYM_P, + "Q" => SYM_Q, + "R" => SYM_R, + "S" => SYM_S, + "T" => SYM_T, + "U" => SYM_U, + "V" => SYM_V, + "W" => SYM_W, + "X" => SYM_X, + "Y" => SYM_Y, + "Z" => SYM_Z, + "bracketleft" => SYM_bracketleft, + "backslash" => SYM_backslash, + "bracketright" => SYM_bracketright, + "asciicircum" => SYM_asciicircum, + "underscore" => SYM_underscore, + "grave" => SYM_grave, + "quoteleft" => SYM_quoteleft, + "a" => SYM_a, + "b" => SYM_b, + "c" => SYM_c, + "d" => SYM_d, + "e" => SYM_e, + "f" => SYM_f, + "g" => SYM_g, + "h" => SYM_h, + "i" => SYM_i, + "j" => SYM_j, + "k" => SYM_k, + "l" => SYM_l, + "m" => SYM_m, + "n" => SYM_n, + "o" => SYM_o, + "p" => SYM_p, + "q" => SYM_q, + "r" => SYM_r, + "s" => SYM_s, + "t" => SYM_t, + "u" => SYM_u, + "v" => SYM_v, + "w" => SYM_w, + "x" => SYM_x, + "y" => SYM_y, + "z" => SYM_z, + "braceleft" => SYM_braceleft, + "bar" => SYM_bar, + "braceright" => SYM_braceright, + "asciitilde" => SYM_asciitilde, + "nobreakspace" => SYM_nobreakspace, + "exclamdown" => SYM_exclamdown, + "cent" => SYM_cent, + "sterling" => SYM_sterling, + "currency" => SYM_currency, + "yen" => SYM_yen, + "brokenbar" => SYM_brokenbar, + "section" => SYM_section, + "diaeresis" => SYM_diaeresis, + "copyright" => SYM_copyright, + "ordfeminine" => SYM_ordfeminine, + "guillemotleft" => SYM_guillemotleft, + "guillemetleft" => SYM_guillemetleft, + "notsign" => SYM_notsign, + "hyphen" => SYM_hyphen, + "registered" => SYM_registered, + "macron" => SYM_macron, + "degree" => SYM_degree, + "plusminus" => SYM_plusminus, + "twosuperior" => SYM_twosuperior, + "threesuperior" => SYM_threesuperior, + "acute" => SYM_acute, + "mu" => SYM_mu, + "paragraph" => SYM_paragraph, + "periodcentered" => SYM_periodcentered, + "cedilla" => SYM_cedilla, + "onesuperior" => SYM_onesuperior, + "masculine" => SYM_masculine, + "ordmasculine" => SYM_ordmasculine, + "guillemotright" => SYM_guillemotright, + "guillemetright" => SYM_guillemetright, + "onequarter" => SYM_onequarter, + "onehalf" => SYM_onehalf, + "threequarters" => SYM_threequarters, + "questiondown" => SYM_questiondown, + "Agrave" => SYM_Agrave, + "Aacute" => SYM_Aacute, + "Acircumflex" => SYM_Acircumflex, + "Atilde" => SYM_Atilde, + "Adiaeresis" => SYM_Adiaeresis, + "Aring" => SYM_Aring, + "AE" => SYM_AE, + "Ccedilla" => SYM_Ccedilla, + "Egrave" => SYM_Egrave, + "Eacute" => SYM_Eacute, + "Ecircumflex" => SYM_Ecircumflex, + "Ediaeresis" => SYM_Ediaeresis, + "Igrave" => SYM_Igrave, + "Iacute" => SYM_Iacute, + "Icircumflex" => SYM_Icircumflex, + "Idiaeresis" => SYM_Idiaeresis, + "ETH" => SYM_ETH, + "Eth" => SYM_Eth, + "Ntilde" => SYM_Ntilde, + "Ograve" => SYM_Ograve, + "Oacute" => SYM_Oacute, + "Ocircumflex" => SYM_Ocircumflex, + "Otilde" => SYM_Otilde, + "Odiaeresis" => SYM_Odiaeresis, + "multiply" => SYM_multiply, + "Oslash" => SYM_Oslash, + "Ooblique" => SYM_Ooblique, + "Ugrave" => SYM_Ugrave, + "Uacute" => SYM_Uacute, + "Ucircumflex" => SYM_Ucircumflex, + "Udiaeresis" => SYM_Udiaeresis, + "Yacute" => SYM_Yacute, + "THORN" => SYM_THORN, + "Thorn" => SYM_Thorn, + "ssharp" => SYM_ssharp, + "agrave" => SYM_agrave, + "aacute" => SYM_aacute, + "acircumflex" => SYM_acircumflex, + "atilde" => SYM_atilde, + "adiaeresis" => SYM_adiaeresis, + "aring" => SYM_aring, + "ae" => SYM_ae, + "ccedilla" => SYM_ccedilla, + "egrave" => SYM_egrave, + "eacute" => SYM_eacute, + "ecircumflex" => SYM_ecircumflex, + "ediaeresis" => SYM_ediaeresis, + "igrave" => SYM_igrave, + "iacute" => SYM_iacute, + "icircumflex" => SYM_icircumflex, + "idiaeresis" => SYM_idiaeresis, + "eth" => SYM_eth, + "ntilde" => SYM_ntilde, + "ograve" => SYM_ograve, + "oacute" => SYM_oacute, + "ocircumflex" => SYM_ocircumflex, + "otilde" => SYM_otilde, + "odiaeresis" => SYM_odiaeresis, + "division" => SYM_division, + "oslash" => SYM_oslash, + "ooblique" => SYM_ooblique, + "ugrave" => SYM_ugrave, + "uacute" => SYM_uacute, + "ucircumflex" => SYM_ucircumflex, + "udiaeresis" => SYM_udiaeresis, + "yacute" => SYM_yacute, + "thorn" => SYM_thorn, + "ydiaeresis" => SYM_ydiaeresis, + "Aogonek" => SYM_Aogonek, + "breve" => SYM_breve, + "Lstroke" => SYM_Lstroke, + "Lcaron" => SYM_Lcaron, + "Sacute" => SYM_Sacute, + "Scaron" => SYM_Scaron, + "Scedilla" => SYM_Scedilla, + "Tcaron" => SYM_Tcaron, + "Zacute" => SYM_Zacute, + "Zcaron" => SYM_Zcaron, + "Zabovedot" => SYM_Zabovedot, + "aogonek" => SYM_aogonek, + "ogonek" => SYM_ogonek, + "lstroke" => SYM_lstroke, + "lcaron" => SYM_lcaron, + "sacute" => SYM_sacute, + "caron" => SYM_caron, + "scaron" => SYM_scaron, + "scedilla" => SYM_scedilla, + "tcaron" => SYM_tcaron, + "zacute" => SYM_zacute, + "doubleacute" => SYM_doubleacute, + "zcaron" => SYM_zcaron, + "zabovedot" => SYM_zabovedot, + "Racute" => SYM_Racute, + "Abreve" => SYM_Abreve, + "Lacute" => SYM_Lacute, + "Cacute" => SYM_Cacute, + "Ccaron" => SYM_Ccaron, + "Eogonek" => SYM_Eogonek, + "Ecaron" => SYM_Ecaron, + "Dcaron" => SYM_Dcaron, + "Dstroke" => SYM_Dstroke, + "Nacute" => SYM_Nacute, + "Ncaron" => SYM_Ncaron, + "Odoubleacute" => SYM_Odoubleacute, + "Rcaron" => SYM_Rcaron, + "Uring" => SYM_Uring, + "Udoubleacute" => SYM_Udoubleacute, + "Tcedilla" => SYM_Tcedilla, + "racute" => SYM_racute, + "abreve" => SYM_abreve, + "lacute" => SYM_lacute, + "cacute" => SYM_cacute, + "ccaron" => SYM_ccaron, + "eogonek" => SYM_eogonek, + "ecaron" => SYM_ecaron, + "dcaron" => SYM_dcaron, + "dstroke" => SYM_dstroke, + "nacute" => SYM_nacute, + "ncaron" => SYM_ncaron, + "odoubleacute" => SYM_odoubleacute, + "rcaron" => SYM_rcaron, + "uring" => SYM_uring, + "udoubleacute" => SYM_udoubleacute, + "tcedilla" => SYM_tcedilla, + "abovedot" => SYM_abovedot, + "Hstroke" => SYM_Hstroke, + "Hcircumflex" => SYM_Hcircumflex, + "Iabovedot" => SYM_Iabovedot, + "Gbreve" => SYM_Gbreve, + "Jcircumflex" => SYM_Jcircumflex, + "hstroke" => SYM_hstroke, + "hcircumflex" => SYM_hcircumflex, + "idotless" => SYM_idotless, + "gbreve" => SYM_gbreve, + "jcircumflex" => SYM_jcircumflex, + "Cabovedot" => SYM_Cabovedot, + "Ccircumflex" => SYM_Ccircumflex, + "Gabovedot" => SYM_Gabovedot, + "Gcircumflex" => SYM_Gcircumflex, + "Ubreve" => SYM_Ubreve, + "Scircumflex" => SYM_Scircumflex, + "cabovedot" => SYM_cabovedot, + "ccircumflex" => SYM_ccircumflex, + "gabovedot" => SYM_gabovedot, + "gcircumflex" => SYM_gcircumflex, + "ubreve" => SYM_ubreve, + "scircumflex" => SYM_scircumflex, + "kra" => SYM_kra, + "kappa" => SYM_kappa, + "Rcedilla" => SYM_Rcedilla, + "Itilde" => SYM_Itilde, + "Lcedilla" => SYM_Lcedilla, + "Emacron" => SYM_Emacron, + "Gcedilla" => SYM_Gcedilla, + "Tslash" => SYM_Tslash, + "rcedilla" => SYM_rcedilla, + "itilde" => SYM_itilde, + "lcedilla" => SYM_lcedilla, + "emacron" => SYM_emacron, + "gcedilla" => SYM_gcedilla, + "tslash" => SYM_tslash, + "ENG" => SYM_ENG, + "eng" => SYM_eng, + "Amacron" => SYM_Amacron, + "Iogonek" => SYM_Iogonek, + "Eabovedot" => SYM_Eabovedot, + "Imacron" => SYM_Imacron, + "Ncedilla" => SYM_Ncedilla, + "Omacron" => SYM_Omacron, + "Kcedilla" => SYM_Kcedilla, + "Uogonek" => SYM_Uogonek, + "Utilde" => SYM_Utilde, + "Umacron" => SYM_Umacron, + "amacron" => SYM_amacron, + "iogonek" => SYM_iogonek, + "eabovedot" => SYM_eabovedot, + "imacron" => SYM_imacron, + "ncedilla" => SYM_ncedilla, + "omacron" => SYM_omacron, + "kcedilla" => SYM_kcedilla, + "uogonek" => SYM_uogonek, + "utilde" => SYM_utilde, + "umacron" => SYM_umacron, + "Wcircumflex" => SYM_Wcircumflex, + "wcircumflex" => SYM_wcircumflex, + "Ycircumflex" => SYM_Ycircumflex, + "ycircumflex" => SYM_ycircumflex, + "Babovedot" => SYM_Babovedot, + "babovedot" => SYM_babovedot, + "Dabovedot" => SYM_Dabovedot, + "dabovedot" => SYM_dabovedot, + "Fabovedot" => SYM_Fabovedot, + "fabovedot" => SYM_fabovedot, + "Mabovedot" => SYM_Mabovedot, + "mabovedot" => SYM_mabovedot, + "Pabovedot" => SYM_Pabovedot, + "pabovedot" => SYM_pabovedot, + "Sabovedot" => SYM_Sabovedot, + "sabovedot" => SYM_sabovedot, + "Tabovedot" => SYM_Tabovedot, + "tabovedot" => SYM_tabovedot, + "Wgrave" => SYM_Wgrave, + "wgrave" => SYM_wgrave, + "Wacute" => SYM_Wacute, + "wacute" => SYM_wacute, + "Wdiaeresis" => SYM_Wdiaeresis, + "wdiaeresis" => SYM_wdiaeresis, + "Ygrave" => SYM_Ygrave, + "ygrave" => SYM_ygrave, + "OE" => SYM_OE, + "oe" => SYM_oe, + "Ydiaeresis" => SYM_Ydiaeresis, + "overline" => SYM_overline, + "kana_fullstop" => SYM_kana_fullstop, + "kana_openingbracket" => SYM_kana_openingbracket, + "kana_closingbracket" => SYM_kana_closingbracket, + "kana_comma" => SYM_kana_comma, + "kana_conjunctive" => SYM_kana_conjunctive, + "kana_middledot" => SYM_kana_middledot, + "kana_WO" => SYM_kana_WO, + "kana_a" => SYM_kana_a, + "kana_i" => SYM_kana_i, + "kana_u" => SYM_kana_u, + "kana_e" => SYM_kana_e, + "kana_o" => SYM_kana_o, + "kana_ya" => SYM_kana_ya, + "kana_yu" => SYM_kana_yu, + "kana_yo" => SYM_kana_yo, + "kana_tsu" => SYM_kana_tsu, + "kana_tu" => SYM_kana_tu, + "prolongedsound" => SYM_prolongedsound, + "kana_A" => SYM_kana_A, + "kana_I" => SYM_kana_I, + "kana_U" => SYM_kana_U, + "kana_E" => SYM_kana_E, + "kana_O" => SYM_kana_O, + "kana_KA" => SYM_kana_KA, + "kana_KI" => SYM_kana_KI, + "kana_KU" => SYM_kana_KU, + "kana_KE" => SYM_kana_KE, + "kana_KO" => SYM_kana_KO, + "kana_SA" => SYM_kana_SA, + "kana_SHI" => SYM_kana_SHI, + "kana_SU" => SYM_kana_SU, + "kana_SE" => SYM_kana_SE, + "kana_SO" => SYM_kana_SO, + "kana_TA" => SYM_kana_TA, + "kana_CHI" => SYM_kana_CHI, + "kana_TI" => SYM_kana_TI, + "kana_TSU" => SYM_kana_TSU, + "kana_TU" => SYM_kana_TU, + "kana_TE" => SYM_kana_TE, + "kana_TO" => SYM_kana_TO, + "kana_NA" => SYM_kana_NA, + "kana_NI" => SYM_kana_NI, + "kana_NU" => SYM_kana_NU, + "kana_NE" => SYM_kana_NE, + "kana_NO" => SYM_kana_NO, + "kana_HA" => SYM_kana_HA, + "kana_HI" => SYM_kana_HI, + "kana_FU" => SYM_kana_FU, + "kana_HU" => SYM_kana_HU, + "kana_HE" => SYM_kana_HE, + "kana_HO" => SYM_kana_HO, + "kana_MA" => SYM_kana_MA, + "kana_MI" => SYM_kana_MI, + "kana_MU" => SYM_kana_MU, + "kana_ME" => SYM_kana_ME, + "kana_MO" => SYM_kana_MO, + "kana_YA" => SYM_kana_YA, + "kana_YU" => SYM_kana_YU, + "kana_YO" => SYM_kana_YO, + "kana_RA" => SYM_kana_RA, + "kana_RI" => SYM_kana_RI, + "kana_RU" => SYM_kana_RU, + "kana_RE" => SYM_kana_RE, + "kana_RO" => SYM_kana_RO, + "kana_WA" => SYM_kana_WA, + "kana_N" => SYM_kana_N, + "voicedsound" => SYM_voicedsound, + "semivoicedsound" => SYM_semivoicedsound, + "kana_switch" => SYM_kana_switch, + "Farsi_0" => SYM_Farsi_0, + "Farsi_1" => SYM_Farsi_1, + "Farsi_2" => SYM_Farsi_2, + "Farsi_3" => SYM_Farsi_3, + "Farsi_4" => SYM_Farsi_4, + "Farsi_5" => SYM_Farsi_5, + "Farsi_6" => SYM_Farsi_6, + "Farsi_7" => SYM_Farsi_7, + "Farsi_8" => SYM_Farsi_8, + "Farsi_9" => SYM_Farsi_9, + "Arabic_percent" => SYM_Arabic_percent, + "Arabic_superscript_alef" => SYM_Arabic_superscript_alef, + "Arabic_tteh" => SYM_Arabic_tteh, + "Arabic_peh" => SYM_Arabic_peh, + "Arabic_tcheh" => SYM_Arabic_tcheh, + "Arabic_ddal" => SYM_Arabic_ddal, + "Arabic_rreh" => SYM_Arabic_rreh, + "Arabic_comma" => SYM_Arabic_comma, + "Arabic_fullstop" => SYM_Arabic_fullstop, + "Arabic_0" => SYM_Arabic_0, + "Arabic_1" => SYM_Arabic_1, + "Arabic_2" => SYM_Arabic_2, + "Arabic_3" => SYM_Arabic_3, + "Arabic_4" => SYM_Arabic_4, + "Arabic_5" => SYM_Arabic_5, + "Arabic_6" => SYM_Arabic_6, + "Arabic_7" => SYM_Arabic_7, + "Arabic_8" => SYM_Arabic_8, + "Arabic_9" => SYM_Arabic_9, + "Arabic_semicolon" => SYM_Arabic_semicolon, + "Arabic_question_mark" => SYM_Arabic_question_mark, + "Arabic_hamza" => SYM_Arabic_hamza, + "Arabic_maddaonalef" => SYM_Arabic_maddaonalef, + "Arabic_hamzaonalef" => SYM_Arabic_hamzaonalef, + "Arabic_hamzaonwaw" => SYM_Arabic_hamzaonwaw, + "Arabic_hamzaunderalef" => SYM_Arabic_hamzaunderalef, + "Arabic_hamzaonyeh" => SYM_Arabic_hamzaonyeh, + "Arabic_alef" => SYM_Arabic_alef, + "Arabic_beh" => SYM_Arabic_beh, + "Arabic_tehmarbuta" => SYM_Arabic_tehmarbuta, + "Arabic_teh" => SYM_Arabic_teh, + "Arabic_theh" => SYM_Arabic_theh, + "Arabic_jeem" => SYM_Arabic_jeem, + "Arabic_hah" => SYM_Arabic_hah, + "Arabic_khah" => SYM_Arabic_khah, + "Arabic_dal" => SYM_Arabic_dal, + "Arabic_thal" => SYM_Arabic_thal, + "Arabic_ra" => SYM_Arabic_ra, + "Arabic_zain" => SYM_Arabic_zain, + "Arabic_seen" => SYM_Arabic_seen, + "Arabic_sheen" => SYM_Arabic_sheen, + "Arabic_sad" => SYM_Arabic_sad, + "Arabic_dad" => SYM_Arabic_dad, + "Arabic_tah" => SYM_Arabic_tah, + "Arabic_zah" => SYM_Arabic_zah, + "Arabic_ain" => SYM_Arabic_ain, + "Arabic_ghain" => SYM_Arabic_ghain, + "Arabic_tatweel" => SYM_Arabic_tatweel, + "Arabic_feh" => SYM_Arabic_feh, + "Arabic_qaf" => SYM_Arabic_qaf, + "Arabic_kaf" => SYM_Arabic_kaf, + "Arabic_lam" => SYM_Arabic_lam, + "Arabic_meem" => SYM_Arabic_meem, + "Arabic_noon" => SYM_Arabic_noon, + "Arabic_ha" => SYM_Arabic_ha, + "Arabic_heh" => SYM_Arabic_heh, + "Arabic_waw" => SYM_Arabic_waw, + "Arabic_alefmaksura" => SYM_Arabic_alefmaksura, + "Arabic_yeh" => SYM_Arabic_yeh, + "Arabic_fathatan" => SYM_Arabic_fathatan, + "Arabic_dammatan" => SYM_Arabic_dammatan, + "Arabic_kasratan" => SYM_Arabic_kasratan, + "Arabic_fatha" => SYM_Arabic_fatha, + "Arabic_damma" => SYM_Arabic_damma, + "Arabic_kasra" => SYM_Arabic_kasra, + "Arabic_shadda" => SYM_Arabic_shadda, + "Arabic_sukun" => SYM_Arabic_sukun, + "Arabic_madda_above" => SYM_Arabic_madda_above, + "Arabic_hamza_above" => SYM_Arabic_hamza_above, + "Arabic_hamza_below" => SYM_Arabic_hamza_below, + "Arabic_jeh" => SYM_Arabic_jeh, + "Arabic_veh" => SYM_Arabic_veh, + "Arabic_keheh" => SYM_Arabic_keheh, + "Arabic_gaf" => SYM_Arabic_gaf, + "Arabic_noon_ghunna" => SYM_Arabic_noon_ghunna, + "Arabic_heh_doachashmee" => SYM_Arabic_heh_doachashmee, + "Farsi_yeh" => SYM_Farsi_yeh, + "Arabic_farsi_yeh" => SYM_Arabic_farsi_yeh, + "Arabic_yeh_baree" => SYM_Arabic_yeh_baree, + "Arabic_heh_goal" => SYM_Arabic_heh_goal, + "Arabic_switch" => SYM_Arabic_switch, + "Cyrillic_GHE_bar" => SYM_Cyrillic_GHE_bar, + "Cyrillic_ghe_bar" => SYM_Cyrillic_ghe_bar, + "Cyrillic_ZHE_descender" => SYM_Cyrillic_ZHE_descender, + "Cyrillic_zhe_descender" => SYM_Cyrillic_zhe_descender, + "Cyrillic_KA_descender" => SYM_Cyrillic_KA_descender, + "Cyrillic_ka_descender" => SYM_Cyrillic_ka_descender, + "Cyrillic_KA_vertstroke" => SYM_Cyrillic_KA_vertstroke, + "Cyrillic_ka_vertstroke" => SYM_Cyrillic_ka_vertstroke, + "Cyrillic_EN_descender" => SYM_Cyrillic_EN_descender, + "Cyrillic_en_descender" => SYM_Cyrillic_en_descender, + "Cyrillic_U_straight" => SYM_Cyrillic_U_straight, + "Cyrillic_u_straight" => SYM_Cyrillic_u_straight, + "Cyrillic_U_straight_bar" => SYM_Cyrillic_U_straight_bar, + "Cyrillic_u_straight_bar" => SYM_Cyrillic_u_straight_bar, + "Cyrillic_HA_descender" => SYM_Cyrillic_HA_descender, + "Cyrillic_ha_descender" => SYM_Cyrillic_ha_descender, + "Cyrillic_CHE_descender" => SYM_Cyrillic_CHE_descender, + "Cyrillic_che_descender" => SYM_Cyrillic_che_descender, + "Cyrillic_CHE_vertstroke" => SYM_Cyrillic_CHE_vertstroke, + "Cyrillic_che_vertstroke" => SYM_Cyrillic_che_vertstroke, + "Cyrillic_SHHA" => SYM_Cyrillic_SHHA, + "Cyrillic_shha" => SYM_Cyrillic_shha, + "Cyrillic_SCHWA" => SYM_Cyrillic_SCHWA, + "Cyrillic_schwa" => SYM_Cyrillic_schwa, + "Cyrillic_I_macron" => SYM_Cyrillic_I_macron, + "Cyrillic_i_macron" => SYM_Cyrillic_i_macron, + "Cyrillic_O_bar" => SYM_Cyrillic_O_bar, + "Cyrillic_o_bar" => SYM_Cyrillic_o_bar, + "Cyrillic_U_macron" => SYM_Cyrillic_U_macron, + "Cyrillic_u_macron" => SYM_Cyrillic_u_macron, + "Serbian_dje" => SYM_Serbian_dje, + "Macedonia_gje" => SYM_Macedonia_gje, + "Cyrillic_io" => SYM_Cyrillic_io, + "Ukrainian_ie" => SYM_Ukrainian_ie, + "Ukranian_je" => SYM_Ukranian_je, + "Macedonia_dse" => SYM_Macedonia_dse, + "Ukrainian_i" => SYM_Ukrainian_i, + "Ukranian_i" => SYM_Ukranian_i, + "Ukrainian_yi" => SYM_Ukrainian_yi, + "Ukranian_yi" => SYM_Ukranian_yi, + "Cyrillic_je" => SYM_Cyrillic_je, + "Serbian_je" => SYM_Serbian_je, + "Cyrillic_lje" => SYM_Cyrillic_lje, + "Serbian_lje" => SYM_Serbian_lje, + "Cyrillic_nje" => SYM_Cyrillic_nje, + "Serbian_nje" => SYM_Serbian_nje, + "Serbian_tshe" => SYM_Serbian_tshe, + "Macedonia_kje" => SYM_Macedonia_kje, + "Ukrainian_ghe_with_upturn" => SYM_Ukrainian_ghe_with_upturn, + "Byelorussian_shortu" => SYM_Byelorussian_shortu, + "Cyrillic_dzhe" => SYM_Cyrillic_dzhe, + "Serbian_dze" => SYM_Serbian_dze, + "numerosign" => SYM_numerosign, + "Serbian_DJE" => SYM_Serbian_DJE, + "Macedonia_GJE" => SYM_Macedonia_GJE, + "Cyrillic_IO" => SYM_Cyrillic_IO, + "Ukrainian_IE" => SYM_Ukrainian_IE, + "Ukranian_JE" => SYM_Ukranian_JE, + "Macedonia_DSE" => SYM_Macedonia_DSE, + "Ukrainian_I" => SYM_Ukrainian_I, + "Ukranian_I" => SYM_Ukranian_I, + "Ukrainian_YI" => SYM_Ukrainian_YI, + "Ukranian_YI" => SYM_Ukranian_YI, + "Cyrillic_JE" => SYM_Cyrillic_JE, + "Serbian_JE" => SYM_Serbian_JE, + "Cyrillic_LJE" => SYM_Cyrillic_LJE, + "Serbian_LJE" => SYM_Serbian_LJE, + "Cyrillic_NJE" => SYM_Cyrillic_NJE, + "Serbian_NJE" => SYM_Serbian_NJE, + "Serbian_TSHE" => SYM_Serbian_TSHE, + "Macedonia_KJE" => SYM_Macedonia_KJE, + "Ukrainian_GHE_WITH_UPTURN" => SYM_Ukrainian_GHE_WITH_UPTURN, + "Byelorussian_SHORTU" => SYM_Byelorussian_SHORTU, + "Cyrillic_DZHE" => SYM_Cyrillic_DZHE, + "Serbian_DZE" => SYM_Serbian_DZE, + "Cyrillic_yu" => SYM_Cyrillic_yu, + "Cyrillic_a" => SYM_Cyrillic_a, + "Cyrillic_be" => SYM_Cyrillic_be, + "Cyrillic_tse" => SYM_Cyrillic_tse, + "Cyrillic_de" => SYM_Cyrillic_de, + "Cyrillic_ie" => SYM_Cyrillic_ie, + "Cyrillic_ef" => SYM_Cyrillic_ef, + "Cyrillic_ghe" => SYM_Cyrillic_ghe, + "Cyrillic_ha" => SYM_Cyrillic_ha, + "Cyrillic_i" => SYM_Cyrillic_i, + "Cyrillic_shorti" => SYM_Cyrillic_shorti, + "Cyrillic_ka" => SYM_Cyrillic_ka, + "Cyrillic_el" => SYM_Cyrillic_el, + "Cyrillic_em" => SYM_Cyrillic_em, + "Cyrillic_en" => SYM_Cyrillic_en, + "Cyrillic_o" => SYM_Cyrillic_o, + "Cyrillic_pe" => SYM_Cyrillic_pe, + "Cyrillic_ya" => SYM_Cyrillic_ya, + "Cyrillic_er" => SYM_Cyrillic_er, + "Cyrillic_es" => SYM_Cyrillic_es, + "Cyrillic_te" => SYM_Cyrillic_te, + "Cyrillic_u" => SYM_Cyrillic_u, + "Cyrillic_zhe" => SYM_Cyrillic_zhe, + "Cyrillic_ve" => SYM_Cyrillic_ve, + "Cyrillic_softsign" => SYM_Cyrillic_softsign, + "Cyrillic_yeru" => SYM_Cyrillic_yeru, + "Cyrillic_ze" => SYM_Cyrillic_ze, + "Cyrillic_sha" => SYM_Cyrillic_sha, + "Cyrillic_e" => SYM_Cyrillic_e, + "Cyrillic_shcha" => SYM_Cyrillic_shcha, + "Cyrillic_che" => SYM_Cyrillic_che, + "Cyrillic_hardsign" => SYM_Cyrillic_hardsign, + "Cyrillic_YU" => SYM_Cyrillic_YU, + "Cyrillic_A" => SYM_Cyrillic_A, + "Cyrillic_BE" => SYM_Cyrillic_BE, + "Cyrillic_TSE" => SYM_Cyrillic_TSE, + "Cyrillic_DE" => SYM_Cyrillic_DE, + "Cyrillic_IE" => SYM_Cyrillic_IE, + "Cyrillic_EF" => SYM_Cyrillic_EF, + "Cyrillic_GHE" => SYM_Cyrillic_GHE, + "Cyrillic_HA" => SYM_Cyrillic_HA, + "Cyrillic_I" => SYM_Cyrillic_I, + "Cyrillic_SHORTI" => SYM_Cyrillic_SHORTI, + "Cyrillic_KA" => SYM_Cyrillic_KA, + "Cyrillic_EL" => SYM_Cyrillic_EL, + "Cyrillic_EM" => SYM_Cyrillic_EM, + "Cyrillic_EN" => SYM_Cyrillic_EN, + "Cyrillic_O" => SYM_Cyrillic_O, + "Cyrillic_PE" => SYM_Cyrillic_PE, + "Cyrillic_YA" => SYM_Cyrillic_YA, + "Cyrillic_ER" => SYM_Cyrillic_ER, + "Cyrillic_ES" => SYM_Cyrillic_ES, + "Cyrillic_TE" => SYM_Cyrillic_TE, + "Cyrillic_U" => SYM_Cyrillic_U, + "Cyrillic_ZHE" => SYM_Cyrillic_ZHE, + "Cyrillic_VE" => SYM_Cyrillic_VE, + "Cyrillic_SOFTSIGN" => SYM_Cyrillic_SOFTSIGN, + "Cyrillic_YERU" => SYM_Cyrillic_YERU, + "Cyrillic_ZE" => SYM_Cyrillic_ZE, + "Cyrillic_SHA" => SYM_Cyrillic_SHA, + "Cyrillic_E" => SYM_Cyrillic_E, + "Cyrillic_SHCHA" => SYM_Cyrillic_SHCHA, + "Cyrillic_CHE" => SYM_Cyrillic_CHE, + "Cyrillic_HARDSIGN" => SYM_Cyrillic_HARDSIGN, + "Greek_ALPHAaccent" => SYM_Greek_ALPHAaccent, + "Greek_EPSILONaccent" => SYM_Greek_EPSILONaccent, + "Greek_ETAaccent" => SYM_Greek_ETAaccent, + "Greek_IOTAaccent" => SYM_Greek_IOTAaccent, + "Greek_IOTAdieresis" => SYM_Greek_IOTAdieresis, + "Greek_IOTAdiaeresis" => SYM_Greek_IOTAdiaeresis, + "Greek_OMICRONaccent" => SYM_Greek_OMICRONaccent, + "Greek_UPSILONaccent" => SYM_Greek_UPSILONaccent, + "Greek_UPSILONdieresis" => SYM_Greek_UPSILONdieresis, + "Greek_OMEGAaccent" => SYM_Greek_OMEGAaccent, + "Greek_accentdieresis" => SYM_Greek_accentdieresis, + "Greek_horizbar" => SYM_Greek_horizbar, + "Greek_alphaaccent" => SYM_Greek_alphaaccent, + "Greek_epsilonaccent" => SYM_Greek_epsilonaccent, + "Greek_etaaccent" => SYM_Greek_etaaccent, + "Greek_iotaaccent" => SYM_Greek_iotaaccent, + "Greek_iotadieresis" => SYM_Greek_iotadieresis, + "Greek_iotaaccentdieresis" => SYM_Greek_iotaaccentdieresis, + "Greek_omicronaccent" => SYM_Greek_omicronaccent, + "Greek_upsilonaccent" => SYM_Greek_upsilonaccent, + "Greek_upsilondieresis" => SYM_Greek_upsilondieresis, + "Greek_upsilonaccentdieresis" => SYM_Greek_upsilonaccentdieresis, + "Greek_omegaaccent" => SYM_Greek_omegaaccent, + "Greek_ALPHA" => SYM_Greek_ALPHA, + "Greek_BETA" => SYM_Greek_BETA, + "Greek_GAMMA" => SYM_Greek_GAMMA, + "Greek_DELTA" => SYM_Greek_DELTA, + "Greek_EPSILON" => SYM_Greek_EPSILON, + "Greek_ZETA" => SYM_Greek_ZETA, + "Greek_ETA" => SYM_Greek_ETA, + "Greek_THETA" => SYM_Greek_THETA, + "Greek_IOTA" => SYM_Greek_IOTA, + "Greek_KAPPA" => SYM_Greek_KAPPA, + "Greek_LAMDA" => SYM_Greek_LAMDA, + "Greek_LAMBDA" => SYM_Greek_LAMBDA, + "Greek_MU" => SYM_Greek_MU, + "Greek_NU" => SYM_Greek_NU, + "Greek_XI" => SYM_Greek_XI, + "Greek_OMICRON" => SYM_Greek_OMICRON, + "Greek_PI" => SYM_Greek_PI, + "Greek_RHO" => SYM_Greek_RHO, + "Greek_SIGMA" => SYM_Greek_SIGMA, + "Greek_TAU" => SYM_Greek_TAU, + "Greek_UPSILON" => SYM_Greek_UPSILON, + "Greek_PHI" => SYM_Greek_PHI, + "Greek_CHI" => SYM_Greek_CHI, + "Greek_PSI" => SYM_Greek_PSI, + "Greek_OMEGA" => SYM_Greek_OMEGA, + "Greek_alpha" => SYM_Greek_alpha, + "Greek_beta" => SYM_Greek_beta, + "Greek_gamma" => SYM_Greek_gamma, + "Greek_delta" => SYM_Greek_delta, + "Greek_epsilon" => SYM_Greek_epsilon, + "Greek_zeta" => SYM_Greek_zeta, + "Greek_eta" => SYM_Greek_eta, + "Greek_theta" => SYM_Greek_theta, + "Greek_iota" => SYM_Greek_iota, + "Greek_kappa" => SYM_Greek_kappa, + "Greek_lamda" => SYM_Greek_lamda, + "Greek_lambda" => SYM_Greek_lambda, + "Greek_mu" => SYM_Greek_mu, + "Greek_nu" => SYM_Greek_nu, + "Greek_xi" => SYM_Greek_xi, + "Greek_omicron" => SYM_Greek_omicron, + "Greek_pi" => SYM_Greek_pi, + "Greek_rho" => SYM_Greek_rho, + "Greek_sigma" => SYM_Greek_sigma, + "Greek_finalsmallsigma" => SYM_Greek_finalsmallsigma, + "Greek_tau" => SYM_Greek_tau, + "Greek_upsilon" => SYM_Greek_upsilon, + "Greek_phi" => SYM_Greek_phi, + "Greek_chi" => SYM_Greek_chi, + "Greek_psi" => SYM_Greek_psi, + "Greek_omega" => SYM_Greek_omega, + "Greek_switch" => SYM_Greek_switch, + "leftradical" => SYM_leftradical, + "topleftradical" => SYM_topleftradical, + "horizconnector" => SYM_horizconnector, + "topintegral" => SYM_topintegral, + "botintegral" => SYM_botintegral, + "vertconnector" => SYM_vertconnector, + "topleftsqbracket" => SYM_topleftsqbracket, + "botleftsqbracket" => SYM_botleftsqbracket, + "toprightsqbracket" => SYM_toprightsqbracket, + "botrightsqbracket" => SYM_botrightsqbracket, + "topleftparens" => SYM_topleftparens, + "botleftparens" => SYM_botleftparens, + "toprightparens" => SYM_toprightparens, + "botrightparens" => SYM_botrightparens, + "leftmiddlecurlybrace" => SYM_leftmiddlecurlybrace, + "rightmiddlecurlybrace" => SYM_rightmiddlecurlybrace, + "topleftsummation" => SYM_topleftsummation, + "botleftsummation" => SYM_botleftsummation, + "topvertsummationconnector" => SYM_topvertsummationconnector, + "botvertsummationconnector" => SYM_botvertsummationconnector, + "toprightsummation" => SYM_toprightsummation, + "botrightsummation" => SYM_botrightsummation, + "rightmiddlesummation" => SYM_rightmiddlesummation, + "lessthanequal" => SYM_lessthanequal, + "notequal" => SYM_notequal, + "greaterthanequal" => SYM_greaterthanequal, + "integral" => SYM_integral, + "therefore" => SYM_therefore, + "variation" => SYM_variation, + "infinity" => SYM_infinity, + "nabla" => SYM_nabla, + "approximate" => SYM_approximate, + "similarequal" => SYM_similarequal, + "ifonlyif" => SYM_ifonlyif, + "implies" => SYM_implies, + "identical" => SYM_identical, + "radical" => SYM_radical, + "includedin" => SYM_includedin, + "includes" => SYM_includes, + "intersection" => SYM_intersection, + "union" => SYM_union, + "logicaland" => SYM_logicaland, + "logicalor" => SYM_logicalor, + "partialderivative" => SYM_partialderivative, + "function" => SYM_function, + "leftarrow" => SYM_leftarrow, + "uparrow" => SYM_uparrow, + "rightarrow" => SYM_rightarrow, + "downarrow" => SYM_downarrow, + "blank" => SYM_blank, + "soliddiamond" => SYM_soliddiamond, + "checkerboard" => SYM_checkerboard, + "ht" => SYM_ht, + "ff" => SYM_ff, + "cr" => SYM_cr, + "lf" => SYM_lf, + "nl" => SYM_nl, + "vt" => SYM_vt, + "lowrightcorner" => SYM_lowrightcorner, + "uprightcorner" => SYM_uprightcorner, + "upleftcorner" => SYM_upleftcorner, + "lowleftcorner" => SYM_lowleftcorner, + "crossinglines" => SYM_crossinglines, + "horizlinescan1" => SYM_horizlinescan1, + "horizlinescan3" => SYM_horizlinescan3, + "horizlinescan5" => SYM_horizlinescan5, + "horizlinescan7" => SYM_horizlinescan7, + "horizlinescan9" => SYM_horizlinescan9, + "leftt" => SYM_leftt, + "rightt" => SYM_rightt, + "bott" => SYM_bott, + "topt" => SYM_topt, + "vertbar" => SYM_vertbar, + "emspace" => SYM_emspace, + "enspace" => SYM_enspace, + "em3space" => SYM_em3space, + "em4space" => SYM_em4space, + "digitspace" => SYM_digitspace, + "punctspace" => SYM_punctspace, + "thinspace" => SYM_thinspace, + "hairspace" => SYM_hairspace, + "emdash" => SYM_emdash, + "endash" => SYM_endash, + "signifblank" => SYM_signifblank, + "ellipsis" => SYM_ellipsis, + "doubbaselinedot" => SYM_doubbaselinedot, + "onethird" => SYM_onethird, + "twothirds" => SYM_twothirds, + "onefifth" => SYM_onefifth, + "twofifths" => SYM_twofifths, + "threefifths" => SYM_threefifths, + "fourfifths" => SYM_fourfifths, + "onesixth" => SYM_onesixth, + "fivesixths" => SYM_fivesixths, + "careof" => SYM_careof, + "figdash" => SYM_figdash, + "leftanglebracket" => SYM_leftanglebracket, + "decimalpoint" => SYM_decimalpoint, + "rightanglebracket" => SYM_rightanglebracket, + "marker" => SYM_marker, + "oneeighth" => SYM_oneeighth, + "threeeighths" => SYM_threeeighths, + "fiveeighths" => SYM_fiveeighths, + "seveneighths" => SYM_seveneighths, + "trademark" => SYM_trademark, + "signaturemark" => SYM_signaturemark, + "trademarkincircle" => SYM_trademarkincircle, + "leftopentriangle" => SYM_leftopentriangle, + "rightopentriangle" => SYM_rightopentriangle, + "emopencircle" => SYM_emopencircle, + "emopenrectangle" => SYM_emopenrectangle, + "leftsinglequotemark" => SYM_leftsinglequotemark, + "rightsinglequotemark" => SYM_rightsinglequotemark, + "leftdoublequotemark" => SYM_leftdoublequotemark, + "rightdoublequotemark" => SYM_rightdoublequotemark, + "prescription" => SYM_prescription, + "permille" => SYM_permille, + "minutes" => SYM_minutes, + "seconds" => SYM_seconds, + "latincross" => SYM_latincross, + "hexagram" => SYM_hexagram, + "filledrectbullet" => SYM_filledrectbullet, + "filledlefttribullet" => SYM_filledlefttribullet, + "filledrighttribullet" => SYM_filledrighttribullet, + "emfilledcircle" => SYM_emfilledcircle, + "emfilledrect" => SYM_emfilledrect, + "enopencircbullet" => SYM_enopencircbullet, + "enopensquarebullet" => SYM_enopensquarebullet, + "openrectbullet" => SYM_openrectbullet, + "opentribulletup" => SYM_opentribulletup, + "opentribulletdown" => SYM_opentribulletdown, + "openstar" => SYM_openstar, + "enfilledcircbullet" => SYM_enfilledcircbullet, + "enfilledsqbullet" => SYM_enfilledsqbullet, + "filledtribulletup" => SYM_filledtribulletup, + "filledtribulletdown" => SYM_filledtribulletdown, + "leftpointer" => SYM_leftpointer, + "rightpointer" => SYM_rightpointer, + "club" => SYM_club, + "diamond" => SYM_diamond, + "heart" => SYM_heart, + "maltesecross" => SYM_maltesecross, + "dagger" => SYM_dagger, + "doubledagger" => SYM_doubledagger, + "checkmark" => SYM_checkmark, + "ballotcross" => SYM_ballotcross, + "musicalsharp" => SYM_musicalsharp, + "musicalflat" => SYM_musicalflat, + "malesymbol" => SYM_malesymbol, + "femalesymbol" => SYM_femalesymbol, + "telephone" => SYM_telephone, + "telephonerecorder" => SYM_telephonerecorder, + "phonographcopyright" => SYM_phonographcopyright, + "caret" => SYM_caret, + "singlelowquotemark" => SYM_singlelowquotemark, + "doublelowquotemark" => SYM_doublelowquotemark, + "cursor" => SYM_cursor, + "leftcaret" => SYM_leftcaret, + "rightcaret" => SYM_rightcaret, + "downcaret" => SYM_downcaret, + "upcaret" => SYM_upcaret, + "overbar" => SYM_overbar, + "downtack" => SYM_downtack, + "upshoe" => SYM_upshoe, + "downstile" => SYM_downstile, + "underbar" => SYM_underbar, + "jot" => SYM_jot, + "quad" => SYM_quad, + "uptack" => SYM_uptack, + "circle" => SYM_circle, + "upstile" => SYM_upstile, + "downshoe" => SYM_downshoe, + "rightshoe" => SYM_rightshoe, + "leftshoe" => SYM_leftshoe, + "lefttack" => SYM_lefttack, + "righttack" => SYM_righttack, + "hebrew_doublelowline" => SYM_hebrew_doublelowline, + "hebrew_aleph" => SYM_hebrew_aleph, + "hebrew_bet" => SYM_hebrew_bet, + "hebrew_beth" => SYM_hebrew_beth, + "hebrew_gimel" => SYM_hebrew_gimel, + "hebrew_gimmel" => SYM_hebrew_gimmel, + "hebrew_dalet" => SYM_hebrew_dalet, + "hebrew_daleth" => SYM_hebrew_daleth, + "hebrew_he" => SYM_hebrew_he, + "hebrew_waw" => SYM_hebrew_waw, + "hebrew_zain" => SYM_hebrew_zain, + "hebrew_zayin" => SYM_hebrew_zayin, + "hebrew_chet" => SYM_hebrew_chet, + "hebrew_het" => SYM_hebrew_het, + "hebrew_tet" => SYM_hebrew_tet, + "hebrew_teth" => SYM_hebrew_teth, + "hebrew_yod" => SYM_hebrew_yod, + "hebrew_finalkaph" => SYM_hebrew_finalkaph, + "hebrew_kaph" => SYM_hebrew_kaph, + "hebrew_lamed" => SYM_hebrew_lamed, + "hebrew_finalmem" => SYM_hebrew_finalmem, + "hebrew_mem" => SYM_hebrew_mem, + "hebrew_finalnun" => SYM_hebrew_finalnun, + "hebrew_nun" => SYM_hebrew_nun, + "hebrew_samech" => SYM_hebrew_samech, + "hebrew_samekh" => SYM_hebrew_samekh, + "hebrew_ayin" => SYM_hebrew_ayin, + "hebrew_finalpe" => SYM_hebrew_finalpe, + "hebrew_pe" => SYM_hebrew_pe, + "hebrew_finalzade" => SYM_hebrew_finalzade, + "hebrew_finalzadi" => SYM_hebrew_finalzadi, + "hebrew_zade" => SYM_hebrew_zade, + "hebrew_zadi" => SYM_hebrew_zadi, + "hebrew_qoph" => SYM_hebrew_qoph, + "hebrew_kuf" => SYM_hebrew_kuf, + "hebrew_resh" => SYM_hebrew_resh, + "hebrew_shin" => SYM_hebrew_shin, + "hebrew_taw" => SYM_hebrew_taw, + "hebrew_taf" => SYM_hebrew_taf, + "Hebrew_switch" => SYM_Hebrew_switch, + "Thai_kokai" => SYM_Thai_kokai, + "Thai_khokhai" => SYM_Thai_khokhai, + "Thai_khokhuat" => SYM_Thai_khokhuat, + "Thai_khokhwai" => SYM_Thai_khokhwai, + "Thai_khokhon" => SYM_Thai_khokhon, + "Thai_khorakhang" => SYM_Thai_khorakhang, + "Thai_ngongu" => SYM_Thai_ngongu, + "Thai_chochan" => SYM_Thai_chochan, + "Thai_choching" => SYM_Thai_choching, + "Thai_chochang" => SYM_Thai_chochang, + "Thai_soso" => SYM_Thai_soso, + "Thai_chochoe" => SYM_Thai_chochoe, + "Thai_yoying" => SYM_Thai_yoying, + "Thai_dochada" => SYM_Thai_dochada, + "Thai_topatak" => SYM_Thai_topatak, + "Thai_thothan" => SYM_Thai_thothan, + "Thai_thonangmontho" => SYM_Thai_thonangmontho, + "Thai_thophuthao" => SYM_Thai_thophuthao, + "Thai_nonen" => SYM_Thai_nonen, + "Thai_dodek" => SYM_Thai_dodek, + "Thai_totao" => SYM_Thai_totao, + "Thai_thothung" => SYM_Thai_thothung, + "Thai_thothahan" => SYM_Thai_thothahan, + "Thai_thothong" => SYM_Thai_thothong, + "Thai_nonu" => SYM_Thai_nonu, + "Thai_bobaimai" => SYM_Thai_bobaimai, + "Thai_popla" => SYM_Thai_popla, + "Thai_phophung" => SYM_Thai_phophung, + "Thai_fofa" => SYM_Thai_fofa, + "Thai_phophan" => SYM_Thai_phophan, + "Thai_fofan" => SYM_Thai_fofan, + "Thai_phosamphao" => SYM_Thai_phosamphao, + "Thai_moma" => SYM_Thai_moma, + "Thai_yoyak" => SYM_Thai_yoyak, + "Thai_rorua" => SYM_Thai_rorua, + "Thai_ru" => SYM_Thai_ru, + "Thai_loling" => SYM_Thai_loling, + "Thai_lu" => SYM_Thai_lu, + "Thai_wowaen" => SYM_Thai_wowaen, + "Thai_sosala" => SYM_Thai_sosala, + "Thai_sorusi" => SYM_Thai_sorusi, + "Thai_sosua" => SYM_Thai_sosua, + "Thai_hohip" => SYM_Thai_hohip, + "Thai_lochula" => SYM_Thai_lochula, + "Thai_oang" => SYM_Thai_oang, + "Thai_honokhuk" => SYM_Thai_honokhuk, + "Thai_paiyannoi" => SYM_Thai_paiyannoi, + "Thai_saraa" => SYM_Thai_saraa, + "Thai_maihanakat" => SYM_Thai_maihanakat, + "Thai_saraaa" => SYM_Thai_saraaa, + "Thai_saraam" => SYM_Thai_saraam, + "Thai_sarai" => SYM_Thai_sarai, + "Thai_saraii" => SYM_Thai_saraii, + "Thai_saraue" => SYM_Thai_saraue, + "Thai_sarauee" => SYM_Thai_sarauee, + "Thai_sarau" => SYM_Thai_sarau, + "Thai_sarauu" => SYM_Thai_sarauu, + "Thai_phinthu" => SYM_Thai_phinthu, + "Thai_maihanakat_maitho" => SYM_Thai_maihanakat_maitho, + "Thai_baht" => SYM_Thai_baht, + "Thai_sarae" => SYM_Thai_sarae, + "Thai_saraae" => SYM_Thai_saraae, + "Thai_sarao" => SYM_Thai_sarao, + "Thai_saraaimaimuan" => SYM_Thai_saraaimaimuan, + "Thai_saraaimaimalai" => SYM_Thai_saraaimaimalai, + "Thai_lakkhangyao" => SYM_Thai_lakkhangyao, + "Thai_maiyamok" => SYM_Thai_maiyamok, + "Thai_maitaikhu" => SYM_Thai_maitaikhu, + "Thai_maiek" => SYM_Thai_maiek, + "Thai_maitho" => SYM_Thai_maitho, + "Thai_maitri" => SYM_Thai_maitri, + "Thai_maichattawa" => SYM_Thai_maichattawa, + "Thai_thanthakhat" => SYM_Thai_thanthakhat, + "Thai_nikhahit" => SYM_Thai_nikhahit, + "Thai_leksun" => SYM_Thai_leksun, + "Thai_leknung" => SYM_Thai_leknung, + "Thai_leksong" => SYM_Thai_leksong, + "Thai_leksam" => SYM_Thai_leksam, + "Thai_leksi" => SYM_Thai_leksi, + "Thai_lekha" => SYM_Thai_lekha, + "Thai_lekhok" => SYM_Thai_lekhok, + "Thai_lekchet" => SYM_Thai_lekchet, + "Thai_lekpaet" => SYM_Thai_lekpaet, + "Thai_lekkao" => SYM_Thai_lekkao, + "Hangul" => SYM_Hangul, + "Hangul_Start" => SYM_Hangul_Start, + "Hangul_End" => SYM_Hangul_End, + "Hangul_Hanja" => SYM_Hangul_Hanja, + "Hangul_Jamo" => SYM_Hangul_Jamo, + "Hangul_Romaja" => SYM_Hangul_Romaja, + "Hangul_Codeinput" => SYM_Hangul_Codeinput, + "Hangul_Jeonja" => SYM_Hangul_Jeonja, + "Hangul_Banja" => SYM_Hangul_Banja, + "Hangul_PreHanja" => SYM_Hangul_PreHanja, + "Hangul_PostHanja" => SYM_Hangul_PostHanja, + "Hangul_SingleCandidate" => SYM_Hangul_SingleCandidate, + "Hangul_MultipleCandidate" => SYM_Hangul_MultipleCandidate, + "Hangul_PreviousCandidate" => SYM_Hangul_PreviousCandidate, + "Hangul_Special" => SYM_Hangul_Special, + "Hangul_switch" => SYM_Hangul_switch, + "Hangul_Kiyeog" => SYM_Hangul_Kiyeog, + "Hangul_SsangKiyeog" => SYM_Hangul_SsangKiyeog, + "Hangul_KiyeogSios" => SYM_Hangul_KiyeogSios, + "Hangul_Nieun" => SYM_Hangul_Nieun, + "Hangul_NieunJieuj" => SYM_Hangul_NieunJieuj, + "Hangul_NieunHieuh" => SYM_Hangul_NieunHieuh, + "Hangul_Dikeud" => SYM_Hangul_Dikeud, + "Hangul_SsangDikeud" => SYM_Hangul_SsangDikeud, + "Hangul_Rieul" => SYM_Hangul_Rieul, + "Hangul_RieulKiyeog" => SYM_Hangul_RieulKiyeog, + "Hangul_RieulMieum" => SYM_Hangul_RieulMieum, + "Hangul_RieulPieub" => SYM_Hangul_RieulPieub, + "Hangul_RieulSios" => SYM_Hangul_RieulSios, + "Hangul_RieulTieut" => SYM_Hangul_RieulTieut, + "Hangul_RieulPhieuf" => SYM_Hangul_RieulPhieuf, + "Hangul_RieulHieuh" => SYM_Hangul_RieulHieuh, + "Hangul_Mieum" => SYM_Hangul_Mieum, + "Hangul_Pieub" => SYM_Hangul_Pieub, + "Hangul_SsangPieub" => SYM_Hangul_SsangPieub, + "Hangul_PieubSios" => SYM_Hangul_PieubSios, + "Hangul_Sios" => SYM_Hangul_Sios, + "Hangul_SsangSios" => SYM_Hangul_SsangSios, + "Hangul_Ieung" => SYM_Hangul_Ieung, + "Hangul_Jieuj" => SYM_Hangul_Jieuj, + "Hangul_SsangJieuj" => SYM_Hangul_SsangJieuj, + "Hangul_Cieuc" => SYM_Hangul_Cieuc, + "Hangul_Khieuq" => SYM_Hangul_Khieuq, + "Hangul_Tieut" => SYM_Hangul_Tieut, + "Hangul_Phieuf" => SYM_Hangul_Phieuf, + "Hangul_Hieuh" => SYM_Hangul_Hieuh, + "Hangul_A" => SYM_Hangul_A, + "Hangul_AE" => SYM_Hangul_AE, + "Hangul_YA" => SYM_Hangul_YA, + "Hangul_YAE" => SYM_Hangul_YAE, + "Hangul_EO" => SYM_Hangul_EO, + "Hangul_E" => SYM_Hangul_E, + "Hangul_YEO" => SYM_Hangul_YEO, + "Hangul_YE" => SYM_Hangul_YE, + "Hangul_O" => SYM_Hangul_O, + "Hangul_WA" => SYM_Hangul_WA, + "Hangul_WAE" => SYM_Hangul_WAE, + "Hangul_OE" => SYM_Hangul_OE, + "Hangul_YO" => SYM_Hangul_YO, + "Hangul_U" => SYM_Hangul_U, + "Hangul_WEO" => SYM_Hangul_WEO, + "Hangul_WE" => SYM_Hangul_WE, + "Hangul_WI" => SYM_Hangul_WI, + "Hangul_YU" => SYM_Hangul_YU, + "Hangul_EU" => SYM_Hangul_EU, + "Hangul_YI" => SYM_Hangul_YI, + "Hangul_I" => SYM_Hangul_I, + "Hangul_J_Kiyeog" => SYM_Hangul_J_Kiyeog, + "Hangul_J_SsangKiyeog" => SYM_Hangul_J_SsangKiyeog, + "Hangul_J_KiyeogSios" => SYM_Hangul_J_KiyeogSios, + "Hangul_J_Nieun" => SYM_Hangul_J_Nieun, + "Hangul_J_NieunJieuj" => SYM_Hangul_J_NieunJieuj, + "Hangul_J_NieunHieuh" => SYM_Hangul_J_NieunHieuh, + "Hangul_J_Dikeud" => SYM_Hangul_J_Dikeud, + "Hangul_J_Rieul" => SYM_Hangul_J_Rieul, + "Hangul_J_RieulKiyeog" => SYM_Hangul_J_RieulKiyeog, + "Hangul_J_RieulMieum" => SYM_Hangul_J_RieulMieum, + "Hangul_J_RieulPieub" => SYM_Hangul_J_RieulPieub, + "Hangul_J_RieulSios" => SYM_Hangul_J_RieulSios, + "Hangul_J_RieulTieut" => SYM_Hangul_J_RieulTieut, + "Hangul_J_RieulPhieuf" => SYM_Hangul_J_RieulPhieuf, + "Hangul_J_RieulHieuh" => SYM_Hangul_J_RieulHieuh, + "Hangul_J_Mieum" => SYM_Hangul_J_Mieum, + "Hangul_J_Pieub" => SYM_Hangul_J_Pieub, + "Hangul_J_PieubSios" => SYM_Hangul_J_PieubSios, + "Hangul_J_Sios" => SYM_Hangul_J_Sios, + "Hangul_J_SsangSios" => SYM_Hangul_J_SsangSios, + "Hangul_J_Ieung" => SYM_Hangul_J_Ieung, + "Hangul_J_Jieuj" => SYM_Hangul_J_Jieuj, + "Hangul_J_Cieuc" => SYM_Hangul_J_Cieuc, + "Hangul_J_Khieuq" => SYM_Hangul_J_Khieuq, + "Hangul_J_Tieut" => SYM_Hangul_J_Tieut, + "Hangul_J_Phieuf" => SYM_Hangul_J_Phieuf, + "Hangul_J_Hieuh" => SYM_Hangul_J_Hieuh, + "Hangul_RieulYeorinHieuh" => SYM_Hangul_RieulYeorinHieuh, + "Hangul_SunkyeongeumMieum" => SYM_Hangul_SunkyeongeumMieum, + "Hangul_SunkyeongeumPieub" => SYM_Hangul_SunkyeongeumPieub, + "Hangul_PanSios" => SYM_Hangul_PanSios, + "Hangul_KkogjiDalrinIeung" => SYM_Hangul_KkogjiDalrinIeung, + "Hangul_SunkyeongeumPhieuf" => SYM_Hangul_SunkyeongeumPhieuf, + "Hangul_YeorinHieuh" => SYM_Hangul_YeorinHieuh, + "Hangul_AraeA" => SYM_Hangul_AraeA, + "Hangul_AraeAE" => SYM_Hangul_AraeAE, + "Hangul_J_PanSios" => SYM_Hangul_J_PanSios, + "Hangul_J_KkogjiDalrinIeung" => SYM_Hangul_J_KkogjiDalrinIeung, + "Hangul_J_YeorinHieuh" => SYM_Hangul_J_YeorinHieuh, + "Korean_Won" => SYM_Korean_Won, + "Armenian_ligature_ew" => SYM_Armenian_ligature_ew, + "Armenian_full_stop" => SYM_Armenian_full_stop, + "Armenian_verjaket" => SYM_Armenian_verjaket, + "Armenian_separation_mark" => SYM_Armenian_separation_mark, + "Armenian_but" => SYM_Armenian_but, + "Armenian_hyphen" => SYM_Armenian_hyphen, + "Armenian_yentamna" => SYM_Armenian_yentamna, + "Armenian_exclam" => SYM_Armenian_exclam, + "Armenian_amanak" => SYM_Armenian_amanak, + "Armenian_accent" => SYM_Armenian_accent, + "Armenian_shesht" => SYM_Armenian_shesht, + "Armenian_question" => SYM_Armenian_question, + "Armenian_paruyk" => SYM_Armenian_paruyk, + "Armenian_AYB" => SYM_Armenian_AYB, + "Armenian_ayb" => SYM_Armenian_ayb, + "Armenian_BEN" => SYM_Armenian_BEN, + "Armenian_ben" => SYM_Armenian_ben, + "Armenian_GIM" => SYM_Armenian_GIM, + "Armenian_gim" => SYM_Armenian_gim, + "Armenian_DA" => SYM_Armenian_DA, + "Armenian_da" => SYM_Armenian_da, + "Armenian_YECH" => SYM_Armenian_YECH, + "Armenian_yech" => SYM_Armenian_yech, + "Armenian_ZA" => SYM_Armenian_ZA, + "Armenian_za" => SYM_Armenian_za, + "Armenian_E" => SYM_Armenian_E, + "Armenian_e" => SYM_Armenian_e, + "Armenian_AT" => SYM_Armenian_AT, + "Armenian_at" => SYM_Armenian_at, + "Armenian_TO" => SYM_Armenian_TO, + "Armenian_to" => SYM_Armenian_to, + "Armenian_ZHE" => SYM_Armenian_ZHE, + "Armenian_zhe" => SYM_Armenian_zhe, + "Armenian_INI" => SYM_Armenian_INI, + "Armenian_ini" => SYM_Armenian_ini, + "Armenian_LYUN" => SYM_Armenian_LYUN, + "Armenian_lyun" => SYM_Armenian_lyun, + "Armenian_KHE" => SYM_Armenian_KHE, + "Armenian_khe" => SYM_Armenian_khe, + "Armenian_TSA" => SYM_Armenian_TSA, + "Armenian_tsa" => SYM_Armenian_tsa, + "Armenian_KEN" => SYM_Armenian_KEN, + "Armenian_ken" => SYM_Armenian_ken, + "Armenian_HO" => SYM_Armenian_HO, + "Armenian_ho" => SYM_Armenian_ho, + "Armenian_DZA" => SYM_Armenian_DZA, + "Armenian_dza" => SYM_Armenian_dza, + "Armenian_GHAT" => SYM_Armenian_GHAT, + "Armenian_ghat" => SYM_Armenian_ghat, + "Armenian_TCHE" => SYM_Armenian_TCHE, + "Armenian_tche" => SYM_Armenian_tche, + "Armenian_MEN" => SYM_Armenian_MEN, + "Armenian_men" => SYM_Armenian_men, + "Armenian_HI" => SYM_Armenian_HI, + "Armenian_hi" => SYM_Armenian_hi, + "Armenian_NU" => SYM_Armenian_NU, + "Armenian_nu" => SYM_Armenian_nu, + "Armenian_SHA" => SYM_Armenian_SHA, + "Armenian_sha" => SYM_Armenian_sha, + "Armenian_VO" => SYM_Armenian_VO, + "Armenian_vo" => SYM_Armenian_vo, + "Armenian_CHA" => SYM_Armenian_CHA, + "Armenian_cha" => SYM_Armenian_cha, + "Armenian_PE" => SYM_Armenian_PE, + "Armenian_pe" => SYM_Armenian_pe, + "Armenian_JE" => SYM_Armenian_JE, + "Armenian_je" => SYM_Armenian_je, + "Armenian_RA" => SYM_Armenian_RA, + "Armenian_ra" => SYM_Armenian_ra, + "Armenian_SE" => SYM_Armenian_SE, + "Armenian_se" => SYM_Armenian_se, + "Armenian_VEV" => SYM_Armenian_VEV, + "Armenian_vev" => SYM_Armenian_vev, + "Armenian_TYUN" => SYM_Armenian_TYUN, + "Armenian_tyun" => SYM_Armenian_tyun, + "Armenian_RE" => SYM_Armenian_RE, + "Armenian_re" => SYM_Armenian_re, + "Armenian_TSO" => SYM_Armenian_TSO, + "Armenian_tso" => SYM_Armenian_tso, + "Armenian_VYUN" => SYM_Armenian_VYUN, + "Armenian_vyun" => SYM_Armenian_vyun, + "Armenian_PYUR" => SYM_Armenian_PYUR, + "Armenian_pyur" => SYM_Armenian_pyur, + "Armenian_KE" => SYM_Armenian_KE, + "Armenian_ke" => SYM_Armenian_ke, + "Armenian_O" => SYM_Armenian_O, + "Armenian_o" => SYM_Armenian_o, + "Armenian_FE" => SYM_Armenian_FE, + "Armenian_fe" => SYM_Armenian_fe, + "Armenian_apostrophe" => SYM_Armenian_apostrophe, + "Georgian_an" => SYM_Georgian_an, + "Georgian_ban" => SYM_Georgian_ban, + "Georgian_gan" => SYM_Georgian_gan, + "Georgian_don" => SYM_Georgian_don, + "Georgian_en" => SYM_Georgian_en, + "Georgian_vin" => SYM_Georgian_vin, + "Georgian_zen" => SYM_Georgian_zen, + "Georgian_tan" => SYM_Georgian_tan, + "Georgian_in" => SYM_Georgian_in, + "Georgian_kan" => SYM_Georgian_kan, + "Georgian_las" => SYM_Georgian_las, + "Georgian_man" => SYM_Georgian_man, + "Georgian_nar" => SYM_Georgian_nar, + "Georgian_on" => SYM_Georgian_on, + "Georgian_par" => SYM_Georgian_par, + "Georgian_zhar" => SYM_Georgian_zhar, + "Georgian_rae" => SYM_Georgian_rae, + "Georgian_san" => SYM_Georgian_san, + "Georgian_tar" => SYM_Georgian_tar, + "Georgian_un" => SYM_Georgian_un, + "Georgian_phar" => SYM_Georgian_phar, + "Georgian_khar" => SYM_Georgian_khar, + "Georgian_ghan" => SYM_Georgian_ghan, + "Georgian_qar" => SYM_Georgian_qar, + "Georgian_shin" => SYM_Georgian_shin, + "Georgian_chin" => SYM_Georgian_chin, + "Georgian_can" => SYM_Georgian_can, + "Georgian_jil" => SYM_Georgian_jil, + "Georgian_cil" => SYM_Georgian_cil, + "Georgian_char" => SYM_Georgian_char, + "Georgian_xan" => SYM_Georgian_xan, + "Georgian_jhan" => SYM_Georgian_jhan, + "Georgian_hae" => SYM_Georgian_hae, + "Georgian_he" => SYM_Georgian_he, + "Georgian_hie" => SYM_Georgian_hie, + "Georgian_we" => SYM_Georgian_we, + "Georgian_har" => SYM_Georgian_har, + "Georgian_hoe" => SYM_Georgian_hoe, + "Georgian_fi" => SYM_Georgian_fi, + "Xabovedot" => SYM_Xabovedot, + "Ibreve" => SYM_Ibreve, + "Zstroke" => SYM_Zstroke, + "Gcaron" => SYM_Gcaron, + "Ocaron" => SYM_Ocaron, + "Obarred" => SYM_Obarred, + "xabovedot" => SYM_xabovedot, + "ibreve" => SYM_ibreve, + "zstroke" => SYM_zstroke, + "gcaron" => SYM_gcaron, + "ocaron" => SYM_ocaron, + "obarred" => SYM_obarred, + "SCHWA" => SYM_SCHWA, + "schwa" => SYM_schwa, + "EZH" => SYM_EZH, + "ezh" => SYM_ezh, + "Lbelowdot" => SYM_Lbelowdot, + "lbelowdot" => SYM_lbelowdot, + "Abelowdot" => SYM_Abelowdot, + "abelowdot" => SYM_abelowdot, + "Ahook" => SYM_Ahook, + "ahook" => SYM_ahook, + "Acircumflexacute" => SYM_Acircumflexacute, + "acircumflexacute" => SYM_acircumflexacute, + "Acircumflexgrave" => SYM_Acircumflexgrave, + "acircumflexgrave" => SYM_acircumflexgrave, + "Acircumflexhook" => SYM_Acircumflexhook, + "acircumflexhook" => SYM_acircumflexhook, + "Acircumflextilde" => SYM_Acircumflextilde, + "acircumflextilde" => SYM_acircumflextilde, + "Acircumflexbelowdot" => SYM_Acircumflexbelowdot, + "acircumflexbelowdot" => SYM_acircumflexbelowdot, + "Abreveacute" => SYM_Abreveacute, + "abreveacute" => SYM_abreveacute, + "Abrevegrave" => SYM_Abrevegrave, + "abrevegrave" => SYM_abrevegrave, + "Abrevehook" => SYM_Abrevehook, + "abrevehook" => SYM_abrevehook, + "Abrevetilde" => SYM_Abrevetilde, + "abrevetilde" => SYM_abrevetilde, + "Abrevebelowdot" => SYM_Abrevebelowdot, + "abrevebelowdot" => SYM_abrevebelowdot, + "Ebelowdot" => SYM_Ebelowdot, + "ebelowdot" => SYM_ebelowdot, + "Ehook" => SYM_Ehook, + "ehook" => SYM_ehook, + "Etilde" => SYM_Etilde, + "etilde" => SYM_etilde, + "Ecircumflexacute" => SYM_Ecircumflexacute, + "ecircumflexacute" => SYM_ecircumflexacute, + "Ecircumflexgrave" => SYM_Ecircumflexgrave, + "ecircumflexgrave" => SYM_ecircumflexgrave, + "Ecircumflexhook" => SYM_Ecircumflexhook, + "ecircumflexhook" => SYM_ecircumflexhook, + "Ecircumflextilde" => SYM_Ecircumflextilde, + "ecircumflextilde" => SYM_ecircumflextilde, + "Ecircumflexbelowdot" => SYM_Ecircumflexbelowdot, + "ecircumflexbelowdot" => SYM_ecircumflexbelowdot, + "Ihook" => SYM_Ihook, + "ihook" => SYM_ihook, + "Ibelowdot" => SYM_Ibelowdot, + "ibelowdot" => SYM_ibelowdot, + "Obelowdot" => SYM_Obelowdot, + "obelowdot" => SYM_obelowdot, + "Ohook" => SYM_Ohook, + "ohook" => SYM_ohook, + "Ocircumflexacute" => SYM_Ocircumflexacute, + "ocircumflexacute" => SYM_ocircumflexacute, + "Ocircumflexgrave" => SYM_Ocircumflexgrave, + "ocircumflexgrave" => SYM_ocircumflexgrave, + "Ocircumflexhook" => SYM_Ocircumflexhook, + "ocircumflexhook" => SYM_ocircumflexhook, + "Ocircumflextilde" => SYM_Ocircumflextilde, + "ocircumflextilde" => SYM_ocircumflextilde, + "Ocircumflexbelowdot" => SYM_Ocircumflexbelowdot, + "ocircumflexbelowdot" => SYM_ocircumflexbelowdot, + "Ohornacute" => SYM_Ohornacute, + "ohornacute" => SYM_ohornacute, + "Ohorngrave" => SYM_Ohorngrave, + "ohorngrave" => SYM_ohorngrave, + "Ohornhook" => SYM_Ohornhook, + "ohornhook" => SYM_ohornhook, + "Ohorntilde" => SYM_Ohorntilde, + "ohorntilde" => SYM_ohorntilde, + "Ohornbelowdot" => SYM_Ohornbelowdot, + "ohornbelowdot" => SYM_ohornbelowdot, + "Ubelowdot" => SYM_Ubelowdot, + "ubelowdot" => SYM_ubelowdot, + "Uhook" => SYM_Uhook, + "uhook" => SYM_uhook, + "Uhornacute" => SYM_Uhornacute, + "uhornacute" => SYM_uhornacute, + "Uhorngrave" => SYM_Uhorngrave, + "uhorngrave" => SYM_uhorngrave, + "Uhornhook" => SYM_Uhornhook, + "uhornhook" => SYM_uhornhook, + "Uhorntilde" => SYM_Uhorntilde, + "uhorntilde" => SYM_uhorntilde, + "Uhornbelowdot" => SYM_Uhornbelowdot, + "uhornbelowdot" => SYM_uhornbelowdot, + "Ybelowdot" => SYM_Ybelowdot, + "ybelowdot" => SYM_ybelowdot, + "Yhook" => SYM_Yhook, + "yhook" => SYM_yhook, + "Ytilde" => SYM_Ytilde, + "ytilde" => SYM_ytilde, + "Ohorn" => SYM_Ohorn, + "ohorn" => SYM_ohorn, + "Uhorn" => SYM_Uhorn, + "uhorn" => SYM_uhorn, + "combining_tilde" => SYM_combining_tilde, + "combining_grave" => SYM_combining_grave, + "combining_acute" => SYM_combining_acute, + "combining_hook" => SYM_combining_hook, + "combining_belowdot" => SYM_combining_belowdot, + "EcuSign" => SYM_EcuSign, + "ColonSign" => SYM_ColonSign, + "CruzeiroSign" => SYM_CruzeiroSign, + "FFrancSign" => SYM_FFrancSign, + "LiraSign" => SYM_LiraSign, + "MillSign" => SYM_MillSign, + "NairaSign" => SYM_NairaSign, + "PesetaSign" => SYM_PesetaSign, + "RupeeSign" => SYM_RupeeSign, + "WonSign" => SYM_WonSign, + "NewSheqelSign" => SYM_NewSheqelSign, + "DongSign" => SYM_DongSign, + "EuroSign" => SYM_EuroSign, + "zerosuperior" => SYM_zerosuperior, + "foursuperior" => SYM_foursuperior, + "fivesuperior" => SYM_fivesuperior, + "sixsuperior" => SYM_sixsuperior, + "sevensuperior" => SYM_sevensuperior, + "eightsuperior" => SYM_eightsuperior, + "ninesuperior" => SYM_ninesuperior, + "zerosubscript" => SYM_zerosubscript, + "onesubscript" => SYM_onesubscript, + "twosubscript" => SYM_twosubscript, + "threesubscript" => SYM_threesubscript, + "foursubscript" => SYM_foursubscript, + "fivesubscript" => SYM_fivesubscript, + "sixsubscript" => SYM_sixsubscript, + "sevensubscript" => SYM_sevensubscript, + "eightsubscript" => SYM_eightsubscript, + "ninesubscript" => SYM_ninesubscript, + "partdifferential" => SYM_partdifferential, + "emptyset" => SYM_emptyset, + "elementof" => SYM_elementof, + "notelementof" => SYM_notelementof, + "containsas" => SYM_containsas, + "squareroot" => SYM_squareroot, + "cuberoot" => SYM_cuberoot, + "fourthroot" => SYM_fourthroot, + "dintegral" => SYM_dintegral, + "tintegral" => SYM_tintegral, + "because" => SYM_because, + "approxeq" => SYM_approxeq, + "notapproxeq" => SYM_notapproxeq, + "notidentical" => SYM_notidentical, + "stricteq" => SYM_stricteq, + "braille_dot_1" => SYM_braille_dot_1, + "braille_dot_2" => SYM_braille_dot_2, + "braille_dot_3" => SYM_braille_dot_3, + "braille_dot_4" => SYM_braille_dot_4, + "braille_dot_5" => SYM_braille_dot_5, + "braille_dot_6" => SYM_braille_dot_6, + "braille_dot_7" => SYM_braille_dot_7, + "braille_dot_8" => SYM_braille_dot_8, + "braille_dot_9" => SYM_braille_dot_9, + "braille_dot_10" => SYM_braille_dot_10, + "braille_blank" => SYM_braille_blank, + "braille_dots_1" => SYM_braille_dots_1, + "braille_dots_2" => SYM_braille_dots_2, + "braille_dots_12" => SYM_braille_dots_12, + "braille_dots_3" => SYM_braille_dots_3, + "braille_dots_13" => SYM_braille_dots_13, + "braille_dots_23" => SYM_braille_dots_23, + "braille_dots_123" => SYM_braille_dots_123, + "braille_dots_4" => SYM_braille_dots_4, + "braille_dots_14" => SYM_braille_dots_14, + "braille_dots_24" => SYM_braille_dots_24, + "braille_dots_124" => SYM_braille_dots_124, + "braille_dots_34" => SYM_braille_dots_34, + "braille_dots_134" => SYM_braille_dots_134, + "braille_dots_234" => SYM_braille_dots_234, + "braille_dots_1234" => SYM_braille_dots_1234, + "braille_dots_5" => SYM_braille_dots_5, + "braille_dots_15" => SYM_braille_dots_15, + "braille_dots_25" => SYM_braille_dots_25, + "braille_dots_125" => SYM_braille_dots_125, + "braille_dots_35" => SYM_braille_dots_35, + "braille_dots_135" => SYM_braille_dots_135, + "braille_dots_235" => SYM_braille_dots_235, + "braille_dots_1235" => SYM_braille_dots_1235, + "braille_dots_45" => SYM_braille_dots_45, + "braille_dots_145" => SYM_braille_dots_145, + "braille_dots_245" => SYM_braille_dots_245, + "braille_dots_1245" => SYM_braille_dots_1245, + "braille_dots_345" => SYM_braille_dots_345, + "braille_dots_1345" => SYM_braille_dots_1345, + "braille_dots_2345" => SYM_braille_dots_2345, + "braille_dots_12345" => SYM_braille_dots_12345, + "braille_dots_6" => SYM_braille_dots_6, + "braille_dots_16" => SYM_braille_dots_16, + "braille_dots_26" => SYM_braille_dots_26, + "braille_dots_126" => SYM_braille_dots_126, + "braille_dots_36" => SYM_braille_dots_36, + "braille_dots_136" => SYM_braille_dots_136, + "braille_dots_236" => SYM_braille_dots_236, + "braille_dots_1236" => SYM_braille_dots_1236, + "braille_dots_46" => SYM_braille_dots_46, + "braille_dots_146" => SYM_braille_dots_146, + "braille_dots_246" => SYM_braille_dots_246, + "braille_dots_1246" => SYM_braille_dots_1246, + "braille_dots_346" => SYM_braille_dots_346, + "braille_dots_1346" => SYM_braille_dots_1346, + "braille_dots_2346" => SYM_braille_dots_2346, + "braille_dots_12346" => SYM_braille_dots_12346, + "braille_dots_56" => SYM_braille_dots_56, + "braille_dots_156" => SYM_braille_dots_156, + "braille_dots_256" => SYM_braille_dots_256, + "braille_dots_1256" => SYM_braille_dots_1256, + "braille_dots_356" => SYM_braille_dots_356, + "braille_dots_1356" => SYM_braille_dots_1356, + "braille_dots_2356" => SYM_braille_dots_2356, + "braille_dots_12356" => SYM_braille_dots_12356, + "braille_dots_456" => SYM_braille_dots_456, + "braille_dots_1456" => SYM_braille_dots_1456, + "braille_dots_2456" => SYM_braille_dots_2456, + "braille_dots_12456" => SYM_braille_dots_12456, + "braille_dots_3456" => SYM_braille_dots_3456, + "braille_dots_13456" => SYM_braille_dots_13456, + "braille_dots_23456" => SYM_braille_dots_23456, + "braille_dots_123456" => SYM_braille_dots_123456, + "braille_dots_7" => SYM_braille_dots_7, + "braille_dots_17" => SYM_braille_dots_17, + "braille_dots_27" => SYM_braille_dots_27, + "braille_dots_127" => SYM_braille_dots_127, + "braille_dots_37" => SYM_braille_dots_37, + "braille_dots_137" => SYM_braille_dots_137, + "braille_dots_237" => SYM_braille_dots_237, + "braille_dots_1237" => SYM_braille_dots_1237, + "braille_dots_47" => SYM_braille_dots_47, + "braille_dots_147" => SYM_braille_dots_147, + "braille_dots_247" => SYM_braille_dots_247, + "braille_dots_1247" => SYM_braille_dots_1247, + "braille_dots_347" => SYM_braille_dots_347, + "braille_dots_1347" => SYM_braille_dots_1347, + "braille_dots_2347" => SYM_braille_dots_2347, + "braille_dots_12347" => SYM_braille_dots_12347, + "braille_dots_57" => SYM_braille_dots_57, + "braille_dots_157" => SYM_braille_dots_157, + "braille_dots_257" => SYM_braille_dots_257, + "braille_dots_1257" => SYM_braille_dots_1257, + "braille_dots_357" => SYM_braille_dots_357, + "braille_dots_1357" => SYM_braille_dots_1357, + "braille_dots_2357" => SYM_braille_dots_2357, + "braille_dots_12357" => SYM_braille_dots_12357, + "braille_dots_457" => SYM_braille_dots_457, + "braille_dots_1457" => SYM_braille_dots_1457, + "braille_dots_2457" => SYM_braille_dots_2457, + "braille_dots_12457" => SYM_braille_dots_12457, + "braille_dots_3457" => SYM_braille_dots_3457, + "braille_dots_13457" => SYM_braille_dots_13457, + "braille_dots_23457" => SYM_braille_dots_23457, + "braille_dots_123457" => SYM_braille_dots_123457, + "braille_dots_67" => SYM_braille_dots_67, + "braille_dots_167" => SYM_braille_dots_167, + "braille_dots_267" => SYM_braille_dots_267, + "braille_dots_1267" => SYM_braille_dots_1267, + "braille_dots_367" => SYM_braille_dots_367, + "braille_dots_1367" => SYM_braille_dots_1367, + "braille_dots_2367" => SYM_braille_dots_2367, + "braille_dots_12367" => SYM_braille_dots_12367, + "braille_dots_467" => SYM_braille_dots_467, + "braille_dots_1467" => SYM_braille_dots_1467, + "braille_dots_2467" => SYM_braille_dots_2467, + "braille_dots_12467" => SYM_braille_dots_12467, + "braille_dots_3467" => SYM_braille_dots_3467, + "braille_dots_13467" => SYM_braille_dots_13467, + "braille_dots_23467" => SYM_braille_dots_23467, + "braille_dots_123467" => SYM_braille_dots_123467, + "braille_dots_567" => SYM_braille_dots_567, + "braille_dots_1567" => SYM_braille_dots_1567, + "braille_dots_2567" => SYM_braille_dots_2567, + "braille_dots_12567" => SYM_braille_dots_12567, + "braille_dots_3567" => SYM_braille_dots_3567, + "braille_dots_13567" => SYM_braille_dots_13567, + "braille_dots_23567" => SYM_braille_dots_23567, + "braille_dots_123567" => SYM_braille_dots_123567, + "braille_dots_4567" => SYM_braille_dots_4567, + "braille_dots_14567" => SYM_braille_dots_14567, + "braille_dots_24567" => SYM_braille_dots_24567, + "braille_dots_124567" => SYM_braille_dots_124567, + "braille_dots_34567" => SYM_braille_dots_34567, + "braille_dots_134567" => SYM_braille_dots_134567, + "braille_dots_234567" => SYM_braille_dots_234567, + "braille_dots_1234567" => SYM_braille_dots_1234567, + "braille_dots_8" => SYM_braille_dots_8, + "braille_dots_18" => SYM_braille_dots_18, + "braille_dots_28" => SYM_braille_dots_28, + "braille_dots_128" => SYM_braille_dots_128, + "braille_dots_38" => SYM_braille_dots_38, + "braille_dots_138" => SYM_braille_dots_138, + "braille_dots_238" => SYM_braille_dots_238, + "braille_dots_1238" => SYM_braille_dots_1238, + "braille_dots_48" => SYM_braille_dots_48, + "braille_dots_148" => SYM_braille_dots_148, + "braille_dots_248" => SYM_braille_dots_248, + "braille_dots_1248" => SYM_braille_dots_1248, + "braille_dots_348" => SYM_braille_dots_348, + "braille_dots_1348" => SYM_braille_dots_1348, + "braille_dots_2348" => SYM_braille_dots_2348, + "braille_dots_12348" => SYM_braille_dots_12348, + "braille_dots_58" => SYM_braille_dots_58, + "braille_dots_158" => SYM_braille_dots_158, + "braille_dots_258" => SYM_braille_dots_258, + "braille_dots_1258" => SYM_braille_dots_1258, + "braille_dots_358" => SYM_braille_dots_358, + "braille_dots_1358" => SYM_braille_dots_1358, + "braille_dots_2358" => SYM_braille_dots_2358, + "braille_dots_12358" => SYM_braille_dots_12358, + "braille_dots_458" => SYM_braille_dots_458, + "braille_dots_1458" => SYM_braille_dots_1458, + "braille_dots_2458" => SYM_braille_dots_2458, + "braille_dots_12458" => SYM_braille_dots_12458, + "braille_dots_3458" => SYM_braille_dots_3458, + "braille_dots_13458" => SYM_braille_dots_13458, + "braille_dots_23458" => SYM_braille_dots_23458, + "braille_dots_123458" => SYM_braille_dots_123458, + "braille_dots_68" => SYM_braille_dots_68, + "braille_dots_168" => SYM_braille_dots_168, + "braille_dots_268" => SYM_braille_dots_268, + "braille_dots_1268" => SYM_braille_dots_1268, + "braille_dots_368" => SYM_braille_dots_368, + "braille_dots_1368" => SYM_braille_dots_1368, + "braille_dots_2368" => SYM_braille_dots_2368, + "braille_dots_12368" => SYM_braille_dots_12368, + "braille_dots_468" => SYM_braille_dots_468, + "braille_dots_1468" => SYM_braille_dots_1468, + "braille_dots_2468" => SYM_braille_dots_2468, + "braille_dots_12468" => SYM_braille_dots_12468, + "braille_dots_3468" => SYM_braille_dots_3468, + "braille_dots_13468" => SYM_braille_dots_13468, + "braille_dots_23468" => SYM_braille_dots_23468, + "braille_dots_123468" => SYM_braille_dots_123468, + "braille_dots_568" => SYM_braille_dots_568, + "braille_dots_1568" => SYM_braille_dots_1568, + "braille_dots_2568" => SYM_braille_dots_2568, + "braille_dots_12568" => SYM_braille_dots_12568, + "braille_dots_3568" => SYM_braille_dots_3568, + "braille_dots_13568" => SYM_braille_dots_13568, + "braille_dots_23568" => SYM_braille_dots_23568, + "braille_dots_123568" => SYM_braille_dots_123568, + "braille_dots_4568" => SYM_braille_dots_4568, + "braille_dots_14568" => SYM_braille_dots_14568, + "braille_dots_24568" => SYM_braille_dots_24568, + "braille_dots_124568" => SYM_braille_dots_124568, + "braille_dots_34568" => SYM_braille_dots_34568, + "braille_dots_134568" => SYM_braille_dots_134568, + "braille_dots_234568" => SYM_braille_dots_234568, + "braille_dots_1234568" => SYM_braille_dots_1234568, + "braille_dots_78" => SYM_braille_dots_78, + "braille_dots_178" => SYM_braille_dots_178, + "braille_dots_278" => SYM_braille_dots_278, + "braille_dots_1278" => SYM_braille_dots_1278, + "braille_dots_378" => SYM_braille_dots_378, + "braille_dots_1378" => SYM_braille_dots_1378, + "braille_dots_2378" => SYM_braille_dots_2378, + "braille_dots_12378" => SYM_braille_dots_12378, + "braille_dots_478" => SYM_braille_dots_478, + "braille_dots_1478" => SYM_braille_dots_1478, + "braille_dots_2478" => SYM_braille_dots_2478, + "braille_dots_12478" => SYM_braille_dots_12478, + "braille_dots_3478" => SYM_braille_dots_3478, + "braille_dots_13478" => SYM_braille_dots_13478, + "braille_dots_23478" => SYM_braille_dots_23478, + "braille_dots_123478" => SYM_braille_dots_123478, + "braille_dots_578" => SYM_braille_dots_578, + "braille_dots_1578" => SYM_braille_dots_1578, + "braille_dots_2578" => SYM_braille_dots_2578, + "braille_dots_12578" => SYM_braille_dots_12578, + "braille_dots_3578" => SYM_braille_dots_3578, + "braille_dots_13578" => SYM_braille_dots_13578, + "braille_dots_23578" => SYM_braille_dots_23578, + "braille_dots_123578" => SYM_braille_dots_123578, + "braille_dots_4578" => SYM_braille_dots_4578, + "braille_dots_14578" => SYM_braille_dots_14578, + "braille_dots_24578" => SYM_braille_dots_24578, + "braille_dots_124578" => SYM_braille_dots_124578, + "braille_dots_34578" => SYM_braille_dots_34578, + "braille_dots_134578" => SYM_braille_dots_134578, + "braille_dots_234578" => SYM_braille_dots_234578, + "braille_dots_1234578" => SYM_braille_dots_1234578, + "braille_dots_678" => SYM_braille_dots_678, + "braille_dots_1678" => SYM_braille_dots_1678, + "braille_dots_2678" => SYM_braille_dots_2678, + "braille_dots_12678" => SYM_braille_dots_12678, + "braille_dots_3678" => SYM_braille_dots_3678, + "braille_dots_13678" => SYM_braille_dots_13678, + "braille_dots_23678" => SYM_braille_dots_23678, + "braille_dots_123678" => SYM_braille_dots_123678, + "braille_dots_4678" => SYM_braille_dots_4678, + "braille_dots_14678" => SYM_braille_dots_14678, + "braille_dots_24678" => SYM_braille_dots_24678, + "braille_dots_124678" => SYM_braille_dots_124678, + "braille_dots_34678" => SYM_braille_dots_34678, + "braille_dots_134678" => SYM_braille_dots_134678, + "braille_dots_234678" => SYM_braille_dots_234678, + "braille_dots_1234678" => SYM_braille_dots_1234678, + "braille_dots_5678" => SYM_braille_dots_5678, + "braille_dots_15678" => SYM_braille_dots_15678, + "braille_dots_25678" => SYM_braille_dots_25678, + "braille_dots_125678" => SYM_braille_dots_125678, + "braille_dots_35678" => SYM_braille_dots_35678, + "braille_dots_135678" => SYM_braille_dots_135678, + "braille_dots_235678" => SYM_braille_dots_235678, + "braille_dots_1235678" => SYM_braille_dots_1235678, + "braille_dots_45678" => SYM_braille_dots_45678, + "braille_dots_145678" => SYM_braille_dots_145678, + "braille_dots_245678" => SYM_braille_dots_245678, + "braille_dots_1245678" => SYM_braille_dots_1245678, + "braille_dots_345678" => SYM_braille_dots_345678, + "braille_dots_1345678" => SYM_braille_dots_1345678, + "braille_dots_2345678" => SYM_braille_dots_2345678, + "braille_dots_12345678" => SYM_braille_dots_12345678, + "Sinh_ng" => SYM_Sinh_ng, + "Sinh_h2" => SYM_Sinh_h2, + "Sinh_a" => SYM_Sinh_a, + "Sinh_aa" => SYM_Sinh_aa, + "Sinh_ae" => SYM_Sinh_ae, + "Sinh_aee" => SYM_Sinh_aee, + "Sinh_i" => SYM_Sinh_i, + "Sinh_ii" => SYM_Sinh_ii, + "Sinh_u" => SYM_Sinh_u, + "Sinh_uu" => SYM_Sinh_uu, + "Sinh_ri" => SYM_Sinh_ri, + "Sinh_rii" => SYM_Sinh_rii, + "Sinh_lu" => SYM_Sinh_lu, + "Sinh_luu" => SYM_Sinh_luu, + "Sinh_e" => SYM_Sinh_e, + "Sinh_ee" => SYM_Sinh_ee, + "Sinh_ai" => SYM_Sinh_ai, + "Sinh_o" => SYM_Sinh_o, + "Sinh_oo" => SYM_Sinh_oo, + "Sinh_au" => SYM_Sinh_au, + "Sinh_ka" => SYM_Sinh_ka, + "Sinh_kha" => SYM_Sinh_kha, + "Sinh_ga" => SYM_Sinh_ga, + "Sinh_gha" => SYM_Sinh_gha, + "Sinh_ng2" => SYM_Sinh_ng2, + "Sinh_nga" => SYM_Sinh_nga, + "Sinh_ca" => SYM_Sinh_ca, + "Sinh_cha" => SYM_Sinh_cha, + "Sinh_ja" => SYM_Sinh_ja, + "Sinh_jha" => SYM_Sinh_jha, + "Sinh_nya" => SYM_Sinh_nya, + "Sinh_jnya" => SYM_Sinh_jnya, + "Sinh_nja" => SYM_Sinh_nja, + "Sinh_tta" => SYM_Sinh_tta, + "Sinh_ttha" => SYM_Sinh_ttha, + "Sinh_dda" => SYM_Sinh_dda, + "Sinh_ddha" => SYM_Sinh_ddha, + "Sinh_nna" => SYM_Sinh_nna, + "Sinh_ndda" => SYM_Sinh_ndda, + "Sinh_tha" => SYM_Sinh_tha, + "Sinh_thha" => SYM_Sinh_thha, + "Sinh_dha" => SYM_Sinh_dha, + "Sinh_dhha" => SYM_Sinh_dhha, + "Sinh_na" => SYM_Sinh_na, + "Sinh_ndha" => SYM_Sinh_ndha, + "Sinh_pa" => SYM_Sinh_pa, + "Sinh_pha" => SYM_Sinh_pha, + "Sinh_ba" => SYM_Sinh_ba, + "Sinh_bha" => SYM_Sinh_bha, + "Sinh_ma" => SYM_Sinh_ma, + "Sinh_mba" => SYM_Sinh_mba, + "Sinh_ya" => SYM_Sinh_ya, + "Sinh_ra" => SYM_Sinh_ra, + "Sinh_la" => SYM_Sinh_la, + "Sinh_va" => SYM_Sinh_va, + "Sinh_sha" => SYM_Sinh_sha, + "Sinh_ssha" => SYM_Sinh_ssha, + "Sinh_sa" => SYM_Sinh_sa, + "Sinh_ha" => SYM_Sinh_ha, + "Sinh_lla" => SYM_Sinh_lla, + "Sinh_fa" => SYM_Sinh_fa, + "Sinh_al" => SYM_Sinh_al, + "Sinh_aa2" => SYM_Sinh_aa2, + "Sinh_ae2" => SYM_Sinh_ae2, + "Sinh_aee2" => SYM_Sinh_aee2, + "Sinh_i2" => SYM_Sinh_i2, + "Sinh_ii2" => SYM_Sinh_ii2, + "Sinh_u2" => SYM_Sinh_u2, + "Sinh_uu2" => SYM_Sinh_uu2, + "Sinh_ru2" => SYM_Sinh_ru2, + "Sinh_e2" => SYM_Sinh_e2, + "Sinh_ee2" => SYM_Sinh_ee2, + "Sinh_ai2" => SYM_Sinh_ai2, + "Sinh_o2" => SYM_Sinh_o2, + "Sinh_oo2" => SYM_Sinh_oo2, + "Sinh_au2" => SYM_Sinh_au2, + "Sinh_lu2" => SYM_Sinh_lu2, + "Sinh_ruu2" => SYM_Sinh_ruu2, + "Sinh_luu2" => SYM_Sinh_luu2, + "Sinh_kunddaliya" => SYM_Sinh_kunddaliya, + "XF86ModeLock" => SYM_XF86ModeLock, + "XF86MonBrightnessUp" => SYM_XF86MonBrightnessUp, + "XF86MonBrightnessDown" => SYM_XF86MonBrightnessDown, + "XF86KbdLightOnOff" => SYM_XF86KbdLightOnOff, + "XF86KbdBrightnessUp" => SYM_XF86KbdBrightnessUp, + "XF86KbdBrightnessDown" => SYM_XF86KbdBrightnessDown, + "XF86MonBrightnessCycle" => SYM_XF86MonBrightnessCycle, + "XF86Standby" => SYM_XF86Standby, + "XF86AudioLowerVolume" => SYM_XF86AudioLowerVolume, + "XF86AudioMute" => SYM_XF86AudioMute, + "XF86AudioRaiseVolume" => SYM_XF86AudioRaiseVolume, + "XF86AudioPlay" => SYM_XF86AudioPlay, + "XF86AudioStop" => SYM_XF86AudioStop, + "XF86AudioPrev" => SYM_XF86AudioPrev, + "XF86AudioNext" => SYM_XF86AudioNext, + "XF86HomePage" => SYM_XF86HomePage, + "XF86Mail" => SYM_XF86Mail, + "XF86Start" => SYM_XF86Start, + "XF86Search" => SYM_XF86Search, + "XF86AudioRecord" => SYM_XF86AudioRecord, + "XF86Calculator" => SYM_XF86Calculator, + "XF86Memo" => SYM_XF86Memo, + "XF86ToDoList" => SYM_XF86ToDoList, + "XF86Calendar" => SYM_XF86Calendar, + "XF86PowerDown" => SYM_XF86PowerDown, + "XF86ContrastAdjust" => SYM_XF86ContrastAdjust, + "XF86RockerUp" => SYM_XF86RockerUp, + "XF86RockerDown" => SYM_XF86RockerDown, + "XF86RockerEnter" => SYM_XF86RockerEnter, + "XF86Back" => SYM_XF86Back, + "XF86Forward" => SYM_XF86Forward, + "XF86Stop" => SYM_XF86Stop, + "XF86Refresh" => SYM_XF86Refresh, + "XF86PowerOff" => SYM_XF86PowerOff, + "XF86WakeUp" => SYM_XF86WakeUp, + "XF86Eject" => SYM_XF86Eject, + "XF86ScreenSaver" => SYM_XF86ScreenSaver, + "XF86WWW" => SYM_XF86WWW, + "XF86Sleep" => SYM_XF86Sleep, + "XF86Favorites" => SYM_XF86Favorites, + "XF86AudioPause" => SYM_XF86AudioPause, + "XF86AudioMedia" => SYM_XF86AudioMedia, + "XF86MyComputer" => SYM_XF86MyComputer, + "XF86VendorHome" => SYM_XF86VendorHome, + "XF86LightBulb" => SYM_XF86LightBulb, + "XF86Shop" => SYM_XF86Shop, + "XF86History" => SYM_XF86History, + "XF86OpenURL" => SYM_XF86OpenURL, + "XF86AddFavorite" => SYM_XF86AddFavorite, + "XF86HotLinks" => SYM_XF86HotLinks, + "XF86BrightnessAdjust" => SYM_XF86BrightnessAdjust, + "XF86Finance" => SYM_XF86Finance, + "XF86Community" => SYM_XF86Community, + "XF86AudioRewind" => SYM_XF86AudioRewind, + "XF86BackForward" => SYM_XF86BackForward, + "XF86Launch0" => SYM_XF86Launch0, + "XF86Launch1" => SYM_XF86Launch1, + "XF86Launch2" => SYM_XF86Launch2, + "XF86Launch3" => SYM_XF86Launch3, + "XF86Launch4" => SYM_XF86Launch4, + "XF86Launch5" => SYM_XF86Launch5, + "XF86Launch6" => SYM_XF86Launch6, + "XF86Launch7" => SYM_XF86Launch7, + "XF86Launch8" => SYM_XF86Launch8, + "XF86Launch9" => SYM_XF86Launch9, + "XF86LaunchA" => SYM_XF86LaunchA, + "XF86LaunchB" => SYM_XF86LaunchB, + "XF86LaunchC" => SYM_XF86LaunchC, + "XF86LaunchD" => SYM_XF86LaunchD, + "XF86LaunchE" => SYM_XF86LaunchE, + "XF86LaunchF" => SYM_XF86LaunchF, + "XF86ApplicationLeft" => SYM_XF86ApplicationLeft, + "XF86ApplicationRight" => SYM_XF86ApplicationRight, + "XF86Book" => SYM_XF86Book, + "XF86CD" => SYM_XF86CD, + "XF86Calculater" => SYM_XF86Calculater, + "XF86Clear" => SYM_XF86Clear, + "XF86Close" => SYM_XF86Close, + "XF86Copy" => SYM_XF86Copy, + "XF86Cut" => SYM_XF86Cut, + "XF86Display" => SYM_XF86Display, + "XF86DOS" => SYM_XF86DOS, + "XF86Documents" => SYM_XF86Documents, + "XF86Excel" => SYM_XF86Excel, + "XF86Explorer" => SYM_XF86Explorer, + "XF86Game" => SYM_XF86Game, + "XF86Go" => SYM_XF86Go, + "XF86iTouch" => SYM_XF86iTouch, + "XF86LogOff" => SYM_XF86LogOff, + "XF86Market" => SYM_XF86Market, + "XF86Meeting" => SYM_XF86Meeting, + "XF86MenuKB" => SYM_XF86MenuKB, + "XF86MenuPB" => SYM_XF86MenuPB, + "XF86MySites" => SYM_XF86MySites, + "XF86New" => SYM_XF86New, + "XF86News" => SYM_XF86News, + "XF86OfficeHome" => SYM_XF86OfficeHome, + "XF86Open" => SYM_XF86Open, + "XF86Option" => SYM_XF86Option, + "XF86Paste" => SYM_XF86Paste, + "XF86Phone" => SYM_XF86Phone, + "XF86Q" => SYM_XF86Q, + "XF86Reply" => SYM_XF86Reply, + "XF86Reload" => SYM_XF86Reload, + "XF86RotateWindows" => SYM_XF86RotateWindows, + "XF86RotationPB" => SYM_XF86RotationPB, + "XF86RotationKB" => SYM_XF86RotationKB, + "XF86Save" => SYM_XF86Save, + "XF86ScrollUp" => SYM_XF86ScrollUp, + "XF86ScrollDown" => SYM_XF86ScrollDown, + "XF86ScrollClick" => SYM_XF86ScrollClick, + "XF86Send" => SYM_XF86Send, + "XF86Spell" => SYM_XF86Spell, + "XF86SplitScreen" => SYM_XF86SplitScreen, + "XF86Support" => SYM_XF86Support, + "XF86TaskPane" => SYM_XF86TaskPane, + "XF86Terminal" => SYM_XF86Terminal, + "XF86Tools" => SYM_XF86Tools, + "XF86Travel" => SYM_XF86Travel, + "XF86UserPB" => SYM_XF86UserPB, + "XF86User1KB" => SYM_XF86User1KB, + "XF86User2KB" => SYM_XF86User2KB, + "XF86Video" => SYM_XF86Video, + "XF86WheelButton" => SYM_XF86WheelButton, + "XF86Word" => SYM_XF86Word, + "XF86Xfer" => SYM_XF86Xfer, + "XF86ZoomIn" => SYM_XF86ZoomIn, + "XF86ZoomOut" => SYM_XF86ZoomOut, + "XF86Away" => SYM_XF86Away, + "XF86Messenger" => SYM_XF86Messenger, + "XF86WebCam" => SYM_XF86WebCam, + "XF86MailForward" => SYM_XF86MailForward, + "XF86Pictures" => SYM_XF86Pictures, + "XF86Music" => SYM_XF86Music, + "XF86Battery" => SYM_XF86Battery, + "XF86Bluetooth" => SYM_XF86Bluetooth, + "XF86WLAN" => SYM_XF86WLAN, + "XF86UWB" => SYM_XF86UWB, + "XF86AudioForward" => SYM_XF86AudioForward, + "XF86AudioRepeat" => SYM_XF86AudioRepeat, + "XF86AudioRandomPlay" => SYM_XF86AudioRandomPlay, + "XF86Subtitle" => SYM_XF86Subtitle, + "XF86AudioCycleTrack" => SYM_XF86AudioCycleTrack, + "XF86CycleAngle" => SYM_XF86CycleAngle, + "XF86FrameBack" => SYM_XF86FrameBack, + "XF86FrameForward" => SYM_XF86FrameForward, + "XF86Time" => SYM_XF86Time, + "XF86Select" => SYM_XF86Select, + "XF86View" => SYM_XF86View, + "XF86TopMenu" => SYM_XF86TopMenu, + "XF86Red" => SYM_XF86Red, + "XF86Green" => SYM_XF86Green, + "XF86Yellow" => SYM_XF86Yellow, + "XF86Blue" => SYM_XF86Blue, + "XF86Suspend" => SYM_XF86Suspend, + "XF86Hibernate" => SYM_XF86Hibernate, + "XF86TouchpadToggle" => SYM_XF86TouchpadToggle, + "XF86TouchpadOn" => SYM_XF86TouchpadOn, + "XF86TouchpadOff" => SYM_XF86TouchpadOff, + "XF86AudioMicMute" => SYM_XF86AudioMicMute, + "XF86Keyboard" => SYM_XF86Keyboard, + "XF86WWAN" => SYM_XF86WWAN, + "XF86RFKill" => SYM_XF86RFKill, + "XF86AudioPreset" => SYM_XF86AudioPreset, + "XF86RotationLockToggle" => SYM_XF86RotationLockToggle, + "XF86FullScreen" => SYM_XF86FullScreen, + "XF86Switch_VT_1" => SYM_XF86Switch_VT_1, + "XF86Switch_VT_2" => SYM_XF86Switch_VT_2, + "XF86Switch_VT_3" => SYM_XF86Switch_VT_3, + "XF86Switch_VT_4" => SYM_XF86Switch_VT_4, + "XF86Switch_VT_5" => SYM_XF86Switch_VT_5, + "XF86Switch_VT_6" => SYM_XF86Switch_VT_6, + "XF86Switch_VT_7" => SYM_XF86Switch_VT_7, + "XF86Switch_VT_8" => SYM_XF86Switch_VT_8, + "XF86Switch_VT_9" => SYM_XF86Switch_VT_9, + "XF86Switch_VT_10" => SYM_XF86Switch_VT_10, + "XF86Switch_VT_11" => SYM_XF86Switch_VT_11, + "XF86Switch_VT_12" => SYM_XF86Switch_VT_12, + "XF86Ungrab" => SYM_XF86Ungrab, + "XF86ClearGrab" => SYM_XF86ClearGrab, + "XF86Next_VMode" => SYM_XF86Next_VMode, + "XF86Prev_VMode" => SYM_XF86Prev_VMode, + "XF86LogWindowTree" => SYM_XF86LogWindowTree, + "XF86LogGrabInfo" => SYM_XF86LogGrabInfo, + "XF86BrightnessAuto" => SYM_XF86BrightnessAuto, + "XF86DisplayOff" => SYM_XF86DisplayOff, + "XF86Info" => SYM_XF86Info, + "XF86AspectRatio" => SYM_XF86AspectRatio, + "XF86DVD" => SYM_XF86DVD, + "XF86Audio" => SYM_XF86Audio, + "XF86ChannelUp" => SYM_XF86ChannelUp, + "XF86ChannelDown" => SYM_XF86ChannelDown, + "XF86Break" => SYM_XF86Break, + "XF86VideoPhone" => SYM_XF86VideoPhone, + "XF86ZoomReset" => SYM_XF86ZoomReset, + "XF86Editor" => SYM_XF86Editor, + "XF86GraphicsEditor" => SYM_XF86GraphicsEditor, + "XF86Presentation" => SYM_XF86Presentation, + "XF86Database" => SYM_XF86Database, + "XF86Voicemail" => SYM_XF86Voicemail, + "XF86Addressbook" => SYM_XF86Addressbook, + "XF86DisplayToggle" => SYM_XF86DisplayToggle, + "XF86SpellCheck" => SYM_XF86SpellCheck, + "XF86ContextMenu" => SYM_XF86ContextMenu, + "XF86MediaRepeat" => SYM_XF86MediaRepeat, + "XF8610ChannelsUp" => SYM_XF8610ChannelsUp, + "XF8610ChannelsDown" => SYM_XF8610ChannelsDown, + "XF86Images" => SYM_XF86Images, + "XF86NotificationCenter" => SYM_XF86NotificationCenter, + "XF86PickupPhone" => SYM_XF86PickupPhone, + "XF86HangupPhone" => SYM_XF86HangupPhone, + "XF86Fn" => SYM_XF86Fn, + "XF86Fn_Esc" => SYM_XF86Fn_Esc, + "XF86FnRightShift" => SYM_XF86FnRightShift, + "XF86Numeric0" => SYM_XF86Numeric0, + "XF86Numeric1" => SYM_XF86Numeric1, + "XF86Numeric2" => SYM_XF86Numeric2, + "XF86Numeric3" => SYM_XF86Numeric3, + "XF86Numeric4" => SYM_XF86Numeric4, + "XF86Numeric5" => SYM_XF86Numeric5, + "XF86Numeric6" => SYM_XF86Numeric6, + "XF86Numeric7" => SYM_XF86Numeric7, + "XF86Numeric8" => SYM_XF86Numeric8, + "XF86Numeric9" => SYM_XF86Numeric9, + "XF86NumericStar" => SYM_XF86NumericStar, + "XF86NumericPound" => SYM_XF86NumericPound, + "XF86NumericA" => SYM_XF86NumericA, + "XF86NumericB" => SYM_XF86NumericB, + "XF86NumericC" => SYM_XF86NumericC, + "XF86NumericD" => SYM_XF86NumericD, + "XF86CameraFocus" => SYM_XF86CameraFocus, + "XF86WPSButton" => SYM_XF86WPSButton, + "XF86CameraZoomIn" => SYM_XF86CameraZoomIn, + "XF86CameraZoomOut" => SYM_XF86CameraZoomOut, + "XF86CameraUp" => SYM_XF86CameraUp, + "XF86CameraDown" => SYM_XF86CameraDown, + "XF86CameraLeft" => SYM_XF86CameraLeft, + "XF86CameraRight" => SYM_XF86CameraRight, + "XF86AttendantOn" => SYM_XF86AttendantOn, + "XF86AttendantOff" => SYM_XF86AttendantOff, + "XF86AttendantToggle" => SYM_XF86AttendantToggle, + "XF86LightsToggle" => SYM_XF86LightsToggle, + "XF86ALSToggle" => SYM_XF86ALSToggle, + "XF86Buttonconfig" => SYM_XF86Buttonconfig, + "XF86Taskmanager" => SYM_XF86Taskmanager, + "XF86Journal" => SYM_XF86Journal, + "XF86ControlPanel" => SYM_XF86ControlPanel, + "XF86AppSelect" => SYM_XF86AppSelect, + "XF86Screensaver" => SYM_XF86Screensaver, + "XF86VoiceCommand" => SYM_XF86VoiceCommand, + "XF86Assistant" => SYM_XF86Assistant, + "XF86EmojiPicker" => SYM_XF86EmojiPicker, + "XF86Dictate" => SYM_XF86Dictate, + "XF86CameraAccessEnable" => SYM_XF86CameraAccessEnable, + "XF86CameraAccessDisable" => SYM_XF86CameraAccessDisable, + "XF86CameraAccessToggle" => SYM_XF86CameraAccessToggle, + "XF86BrightnessMin" => SYM_XF86BrightnessMin, + "XF86BrightnessMax" => SYM_XF86BrightnessMax, + "XF86KbdInputAssistPrev" => SYM_XF86KbdInputAssistPrev, + "XF86KbdInputAssistNext" => SYM_XF86KbdInputAssistNext, + "XF86KbdInputAssistPrevgroup" => SYM_XF86KbdInputAssistPrevgroup, + "XF86KbdInputAssistNextgroup" => SYM_XF86KbdInputAssistNextgroup, + "XF86KbdInputAssistAccept" => SYM_XF86KbdInputAssistAccept, + "XF86KbdInputAssistCancel" => SYM_XF86KbdInputAssistCancel, + "XF86RightUp" => SYM_XF86RightUp, + "XF86RightDown" => SYM_XF86RightDown, + "XF86LeftUp" => SYM_XF86LeftUp, + "XF86LeftDown" => SYM_XF86LeftDown, + "XF86RootMenu" => SYM_XF86RootMenu, + "XF86MediaTopMenu" => SYM_XF86MediaTopMenu, + "XF86Numeric11" => SYM_XF86Numeric11, + "XF86Numeric12" => SYM_XF86Numeric12, + "XF86AudioDesc" => SYM_XF86AudioDesc, + "XF863DMode" => SYM_XF863DMode, + "XF86NextFavorite" => SYM_XF86NextFavorite, + "XF86StopRecord" => SYM_XF86StopRecord, + "XF86PauseRecord" => SYM_XF86PauseRecord, + "XF86VOD" => SYM_XF86VOD, + "XF86Unmute" => SYM_XF86Unmute, + "XF86FastReverse" => SYM_XF86FastReverse, + "XF86SlowReverse" => SYM_XF86SlowReverse, + "XF86Data" => SYM_XF86Data, + "XF86OnScreenKeyboard" => SYM_XF86OnScreenKeyboard, + "XF86PrivacyScreenToggle" => SYM_XF86PrivacyScreenToggle, + "XF86SelectiveScreenshot" => SYM_XF86SelectiveScreenshot, + "XF86NextElement" => SYM_XF86NextElement, + "XF86PreviousElement" => SYM_XF86PreviousElement, + "XF86AutopilotEngageToggle" => SYM_XF86AutopilotEngageToggle, + "XF86MarkWaypoint" => SYM_XF86MarkWaypoint, + "XF86Sos" => SYM_XF86Sos, + "XF86NavChart" => SYM_XF86NavChart, + "XF86FishingChart" => SYM_XF86FishingChart, + "XF86SingleRangeRadar" => SYM_XF86SingleRangeRadar, + "XF86DualRangeRadar" => SYM_XF86DualRangeRadar, + "XF86RadarOverlay" => SYM_XF86RadarOverlay, + "XF86TraditionalSonar" => SYM_XF86TraditionalSonar, + "XF86ClearvuSonar" => SYM_XF86ClearvuSonar, + "XF86SidevuSonar" => SYM_XF86SidevuSonar, + "XF86NavInfo" => SYM_XF86NavInfo, + "XF86Macro1" => SYM_XF86Macro1, + "XF86Macro2" => SYM_XF86Macro2, + "XF86Macro3" => SYM_XF86Macro3, + "XF86Macro4" => SYM_XF86Macro4, + "XF86Macro5" => SYM_XF86Macro5, + "XF86Macro6" => SYM_XF86Macro6, + "XF86Macro7" => SYM_XF86Macro7, + "XF86Macro8" => SYM_XF86Macro8, + "XF86Macro9" => SYM_XF86Macro9, + "XF86Macro10" => SYM_XF86Macro10, + "XF86Macro11" => SYM_XF86Macro11, + "XF86Macro12" => SYM_XF86Macro12, + "XF86Macro13" => SYM_XF86Macro13, + "XF86Macro14" => SYM_XF86Macro14, + "XF86Macro15" => SYM_XF86Macro15, + "XF86Macro16" => SYM_XF86Macro16, + "XF86Macro17" => SYM_XF86Macro17, + "XF86Macro18" => SYM_XF86Macro18, + "XF86Macro19" => SYM_XF86Macro19, + "XF86Macro20" => SYM_XF86Macro20, + "XF86Macro21" => SYM_XF86Macro21, + "XF86Macro22" => SYM_XF86Macro22, + "XF86Macro23" => SYM_XF86Macro23, + "XF86Macro24" => SYM_XF86Macro24, + "XF86Macro25" => SYM_XF86Macro25, + "XF86Macro26" => SYM_XF86Macro26, + "XF86Macro27" => SYM_XF86Macro27, + "XF86Macro28" => SYM_XF86Macro28, + "XF86Macro29" => SYM_XF86Macro29, + "XF86Macro30" => SYM_XF86Macro30, + "XF86MacroRecordStart" => SYM_XF86MacroRecordStart, + "XF86MacroRecordStop" => SYM_XF86MacroRecordStop, + "XF86MacroPresetCycle" => SYM_XF86MacroPresetCycle, + "XF86MacroPreset1" => SYM_XF86MacroPreset1, + "XF86MacroPreset2" => SYM_XF86MacroPreset2, + "XF86MacroPreset3" => SYM_XF86MacroPreset3, + "XF86KbdLcdMenu1" => SYM_XF86KbdLcdMenu1, + "XF86KbdLcdMenu2" => SYM_XF86KbdLcdMenu2, + "XF86KbdLcdMenu3" => SYM_XF86KbdLcdMenu3, + "XF86KbdLcdMenu4" => SYM_XF86KbdLcdMenu4, + "XF86KbdLcdMenu5" => SYM_XF86KbdLcdMenu5, + "SunFA_Grave" => SYM_SunFA_Grave, + "SunFA_Circum" => SYM_SunFA_Circum, + "SunFA_Tilde" => SYM_SunFA_Tilde, + "SunFA_Acute" => SYM_SunFA_Acute, + "SunFA_Diaeresis" => SYM_SunFA_Diaeresis, + "SunFA_Cedilla" => SYM_SunFA_Cedilla, + "SunF36" => SYM_SunF36, + "SunF37" => SYM_SunF37, + "SunSys_Req" => SYM_SunSys_Req, + "SunPrint_Screen" => SYM_SunPrint_Screen, + "SunCompose" => SYM_SunCompose, + "SunAltGraph" => SYM_SunAltGraph, + "SunPageUp" => SYM_SunPageUp, + "SunPageDown" => SYM_SunPageDown, + "SunUndo" => SYM_SunUndo, + "SunAgain" => SYM_SunAgain, + "SunFind" => SYM_SunFind, + "SunStop" => SYM_SunStop, + "SunProps" => SYM_SunProps, + "SunFront" => SYM_SunFront, + "SunCopy" => SYM_SunCopy, + "SunOpen" => SYM_SunOpen, + "SunPaste" => SYM_SunPaste, + "SunCut" => SYM_SunCut, + "SunPowerSwitch" => SYM_SunPowerSwitch, + "SunAudioLowerVolume" => SYM_SunAudioLowerVolume, + "SunAudioMute" => SYM_SunAudioMute, + "SunAudioRaiseVolume" => SYM_SunAudioRaiseVolume, + "SunVideoDegauss" => SYM_SunVideoDegauss, + "SunVideoLowerBrightness" => SYM_SunVideoLowerBrightness, + "SunVideoRaiseBrightness" => SYM_SunVideoRaiseBrightness, + "SunPowerSwitchShift" => SYM_SunPowerSwitchShift, + "Dring_accent" => SYM_Dring_accent, + "Dcircumflex_accent" => SYM_Dcircumflex_accent, + "Dcedilla_accent" => SYM_Dcedilla_accent, + "Dacute_accent" => SYM_Dacute_accent, + "Dgrave_accent" => SYM_Dgrave_accent, + "Dtilde" => SYM_Dtilde, + "Ddiaeresis" => SYM_Ddiaeresis, + "DRemove" => SYM_DRemove, + "hpClearLine" => SYM_hpClearLine, + "hpInsertLine" => SYM_hpInsertLine, + "hpDeleteLine" => SYM_hpDeleteLine, + "hpInsertChar" => SYM_hpInsertChar, + "hpDeleteChar" => SYM_hpDeleteChar, + "hpBackTab" => SYM_hpBackTab, + "hpKP_BackTab" => SYM_hpKP_BackTab, + "hpModelock1" => SYM_hpModelock1, + "hpModelock2" => SYM_hpModelock2, + "hpReset" => SYM_hpReset, + "hpSystem" => SYM_hpSystem, + "hpUser" => SYM_hpUser, + "hpmute_acute" => SYM_hpmute_acute, + "hpmute_grave" => SYM_hpmute_grave, + "hpmute_asciicircum" => SYM_hpmute_asciicircum, + "hpmute_diaeresis" => SYM_hpmute_diaeresis, + "hpmute_asciitilde" => SYM_hpmute_asciitilde, + "hplira" => SYM_hplira, + "hpguilder" => SYM_hpguilder, + "hpYdiaeresis" => SYM_hpYdiaeresis, + "hpIO" => SYM_hpIO, + "hplongminus" => SYM_hplongminus, + "hpblock" => SYM_hpblock, + "osfCopy" => SYM_osfCopy, + "osfCut" => SYM_osfCut, + "osfPaste" => SYM_osfPaste, + "osfBackTab" => SYM_osfBackTab, + "osfBackSpace" => SYM_osfBackSpace, + "osfClear" => SYM_osfClear, + "osfEscape" => SYM_osfEscape, + "osfAddMode" => SYM_osfAddMode, + "osfPrimaryPaste" => SYM_osfPrimaryPaste, + "osfQuickPaste" => SYM_osfQuickPaste, + "osfPageLeft" => SYM_osfPageLeft, + "osfPageUp" => SYM_osfPageUp, + "osfPageDown" => SYM_osfPageDown, + "osfPageRight" => SYM_osfPageRight, + "osfActivate" => SYM_osfActivate, + "osfMenuBar" => SYM_osfMenuBar, + "osfLeft" => SYM_osfLeft, + "osfUp" => SYM_osfUp, + "osfRight" => SYM_osfRight, + "osfDown" => SYM_osfDown, + "osfEndLine" => SYM_osfEndLine, + "osfBeginLine" => SYM_osfBeginLine, + "osfEndData" => SYM_osfEndData, + "osfBeginData" => SYM_osfBeginData, + "osfPrevMenu" => SYM_osfPrevMenu, + "osfNextMenu" => SYM_osfNextMenu, + "osfPrevField" => SYM_osfPrevField, + "osfNextField" => SYM_osfNextField, + "osfSelect" => SYM_osfSelect, + "osfInsert" => SYM_osfInsert, + "osfUndo" => SYM_osfUndo, + "osfMenu" => SYM_osfMenu, + "osfCancel" => SYM_osfCancel, + "osfHelp" => SYM_osfHelp, + "osfSelectAll" => SYM_osfSelectAll, + "osfDeselectAll" => SYM_osfDeselectAll, + "osfReselect" => SYM_osfReselect, + "osfExtend" => SYM_osfExtend, + "osfRestore" => SYM_osfRestore, + "osfDelete" => SYM_osfDelete, + "Reset" => SYM_Reset, + "System" => SYM_System, + "User" => SYM_User, + "ClearLine" => SYM_ClearLine, + "InsertLine" => SYM_InsertLine, + "DeleteLine" => SYM_DeleteLine, + "InsertChar" => SYM_InsertChar, + "DeleteChar" => SYM_DeleteChar, + "BackTab" => SYM_BackTab, + "KP_BackTab" => SYM_KP_BackTab, + "Ext16bit_L" => SYM_Ext16bit_L, + "Ext16bit_R" => SYM_Ext16bit_R, + "mute_acute" => SYM_mute_acute, + "mute_grave" => SYM_mute_grave, + "mute_asciicircum" => SYM_mute_asciicircum, + "mute_diaeresis" => SYM_mute_diaeresis, + "mute_asciitilde" => SYM_mute_asciitilde, + "lira" => SYM_lira, + "guilder" => SYM_guilder, + "IO" => SYM_IO, + "longminus" => SYM_longminus, + "block" => SYM_block, +}; diff --git a/toml-config/src/config/parser.rs b/toml-config/src/config/parser.rs new file mode 100644 index 00000000..68d7b913 --- /dev/null +++ b/toml-config/src/config/parser.rs @@ -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

= Result<

::Value, Spanned<

::Error>>; + +pub trait Parser { + type Value; + type Error: From; + + const EXPECTED: &'static [DataType]; + + fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { + let _ = string; + expected(self, span, DataType::String) + } + + fn parse_integer(&mut self, span: Span, integer: i64) -> ParseResult { + let _ = integer; + expected(self, span, DataType::Integer) + } + + fn parse_float(&mut self, span: Span, float: f64) -> ParseResult { + let _ = float; + expected(self, span, DataType::Float) + } + + fn parse_bool(&mut self, span: Span, bool: bool) -> ParseResult { + let _ = bool; + expected(self, span, DataType::Boolean) + } + + fn parse_array(&mut self, span: Span, array: &[Spanned]) -> ParseResult { + let _ = array; + expected(self, span, DataType::Array) + } + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let _ = table; + expected(self, span, DataType::Table) + } +} + +fn expected(_p: &P, span: Span, actual: DataType) -> ParseResult

{ + Err(P::Error::from(UnexpectedDataType(P::EXPECTED, actual)).spanned(span)) +} diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs new file mode 100644 index 00000000..3d648ca5 --- /dev/null +++ b/toml-config/src/config/parsers.rs @@ -0,0 +1,47 @@ +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 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 { + Ok(string.to_string()) + } +} diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs new file mode 100644 index 00000000..875f0967 --- /dev/null +++ b/toml-config/src/config/parsers/action.rs @@ -0,0 +1,296 @@ +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}, + 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, + 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), +} + +pub struct ActionParser<'a>(pub &'a Context<'a>); + +impl ActionParser<'_> { + fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult { + use crate::config::SimpleCommand::*; + let cmd = match string { + "focus-left" => FocusLeft, + "focus-down" => FocusDown, + "focus-up" => FocusUp, + "focus-right" => FocusRight, + "move-left" => MoveLeft, + "move-down" => MoveDown, + "move-up" => MoveUp, + "move-right" => MoveRight, + "split-horizontal" => SplitHorizontal, + "split-vertical" => SplitVertical, + "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]) -> ParseResult { + 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 { + 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 { + let num = ext.extract(n32("num"))?.value; + Ok(Action::SwitchToVt { num }) + } + + fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let name = ext.extract(str("name"))?.value.to_string(); + Ok(Action::ShowWorkspace { name }) + } + + fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let name = ext.extract(str("name"))?.value.to_string(); + Ok(Action::ShowWorkspace { name }) + } + + fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + 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 { + let input = ext.extract(val("input"))?.parse_map(&mut InputParser { + cx: self.0, + tag_ok: false, + })?; + Ok(Action::ConfigureInput { input }) + } + + fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + 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 { + let env = ext.extract(val("env"))?.parse_map(&mut EnvParser)?; + Ok(Action::SetEnv { env }) + } + + fn parse_unset_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + struct P; + impl Parser for P { + type Value = Vec; + type Error = ActionParserError; + const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String]; + + fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult { + Ok(vec![string.to_string()]) + } + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + let enabled = ext.extract(bol("enabled"))?.value; + Ok(Action::ConfigureDirectScanout { enabled }) + } + + fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + 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.parse_simple_cmd(span, string) + } + + fn parse_array(&mut self, span: Span, array: &[Spanned]) -> ParseResult { + self.parse_multi(span, array) + } + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + 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), + v => { + ext.ignore_unused(); + return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span)); + } + }; + drop(ext); + res + } +} diff --git a/toml-config/src/config/parsers/color.rs b/toml-config/src/config/parsers/color.rs new file mode 100644 index 00000000..2aecaafe --- /dev/null +++ b/toml-config/src/config/parsers/color.rs @@ -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 { + let hex = match string.strip_prefix("#") { + Some(s) => s, + _ => return Err(ColorParserError::Prefix.spanned(span)), + }; + let d = |range: Range| { + u8::from_str_radix(&hex[range], 16) + .map_err(|e| ColorParserError::ParseIntError(e).spanned(span)) + }; + let s = |range: Range| { + 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)) + } +} diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs new file mode 100644 index 00000000..f95417db --- /dev/null +++ b/toml-config/src/config/parsers/config.rs @@ -0,0 +1,266 @@ +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, + 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>) -> Option { + 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>, + ) -> ParseResult { + 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, + _, + ), + ) = 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")), + ), + ))?; + 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)); + } + } + } + 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, + }) + } +} diff --git a/toml-config/src/config/parsers/connector.rs b/toml-config/src/config/parsers/connector.rs new file mode 100644 index 00000000..e4c6a2ae --- /dev/null +++ b/toml-config/src/config/parsers/connector.rs @@ -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>, + ) -> ParseResult { + 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; + type Error = ConnectorParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array]; + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + 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>, + ) -> ParseResult { + log::warn!( + "`connectors` value should be an array: {}", + self.0.error3(span) + ); + ConnectorParser(self.0) + .parse_table(span, table) + .map(|v| vec![v]) + } +} diff --git a/toml-config/src/config/parsers/connector_match.rs b/toml-config/src/config/parsers/connector_match.rs new file mode 100644 index 00000000..f64178a0 --- /dev/null +++ b/toml-config/src/config/parsers/connector_match.rs @@ -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]) -> ParseResult { + 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>, + ) -> ParseResult { + 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()), + }) + } +} diff --git a/toml-config/src/config/parsers/drm_device.rs b/toml-config/src/config/parsers/drm_device.rs new file mode 100644 index 00000000..4a4afd52 --- /dev/null +++ b/toml-config/src/config/parsers/drm_device.rs @@ -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>, + ) -> ParseResult { + 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; + type Error = DrmDeviceParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array]; + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + 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>, + ) -> ParseResult { + 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]) + } +} diff --git a/toml-config/src/config/parsers/drm_device_match.rs b/toml-config/src/config/parsers/drm_device_match.rs new file mode 100644 index 00000000..ec7fe558 --- /dev/null +++ b/toml-config/src/config/parsers/drm_device_match.rs @@ -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]) -> ParseResult { + 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>, + ) -> ParseResult { + 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(), + }) + } +} diff --git a/toml-config/src/config/parsers/env.rs b/toml-config/src/config/parsers/env.rs new file mode 100644 index 00000000..a2a0f9ae --- /dev/null +++ b/toml-config/src/config/parsers/env.rs @@ -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>, + ) -> ParseResult { + let mut envs = vec![]; + for (k, v) in table { + envs.push((k.value.to_string(), v.parse_map(&mut StringParser)?)); + } + Ok(envs) + } +} diff --git a/toml-config/src/config/parsers/exec.rs b/toml-config/src/config/parsers/exec.rs new file mode 100644 index 00000000..41ed9e89 --- /dev/null +++ b/toml-config/src/config/parsers/exec.rs @@ -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 { + Ok(Exec { + prog: string.to_string(), + args: vec![], + envs: vec![], + }) + } + + fn parse_array(&mut self, span: Span, array: &[Spanned]) -> ParseResult { + 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>, + ) -> ParseResult { + 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, + }) + } +} diff --git a/toml-config/src/config/parsers/gfx_api.rs b/toml-config/src/config/parsers/gfx_api.rs new file mode 100644 index 00000000..69bb6740 --- /dev/null +++ b/toml-config/src/config/parsers/gfx_api.rs @@ -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 { + 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) + } +} diff --git a/toml-config/src/config/parsers/input.rs b/toml-config/src/config/parsers/input.rs new file mode 100644 index 00000000..8e8f2c20 --- /dev/null +++ b/toml-config/src/config/parsers/input.rs @@ -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>, + ) -> ParseResult { + 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; + type Error = InputParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array]; + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + 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>, + ) -> ParseResult { + 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]) -> ParseResult { + 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]) -> ParseResult { + if array.len() != 2 { + return Err(InputParserError::TwoColumns.spanned(span)); + } + let extract = |v: &Spanned| 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])?]) + } +} diff --git a/toml-config/src/config/parsers/input_match.rs b/toml-config/src/config/parsers/input_match.rs new file mode 100644 index 00000000..4f0e5b98 --- /dev/null +++ b/toml-config/src/config/parsers/input_match.rs @@ -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]) -> ParseResult { + 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>, + ) -> ParseResult { + 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(), + }) + } +} diff --git a/toml-config/src/config/parsers/keymap.rs b/toml-config/src/config/parsers/keymap.rs new file mode 100644 index 00000000..2ab00208 --- /dev/null +++ b/toml-config/src/config/parsers/keymap.rs @@ -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 { + Ok(ConfigKeymap::Literal(parse(span, string)?)) + } + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + 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> { + let map = parse_keymap(string); + match map.is_valid() { + true => Ok(map), + false => Err(KeymapParserError::Invalid.spanned(span)), + } +} diff --git a/toml-config/src/config/parsers/log_level.rs b/toml-config/src/config/parsers/log_level.rs new file mode 100644 index 00000000..73209d1b --- /dev/null +++ b/toml-config/src/config/parsers/log_level.rs @@ -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 { + 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) + } +} diff --git a/toml-config/src/config/parsers/mode.rs b/toml-config/src/config/parsers/mode.rs new file mode 100644 index 00000000..3482b6d2 --- /dev/null +++ b/toml-config/src/config/parsers/mode.rs @@ -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>, + ) -> ParseResult { + 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(), + }) + } +} diff --git a/toml-config/src/config/parsers/modified_keysym.rs b/toml-config/src/config/parsers/modified_keysym.rs new file mode 100644 index 00000000..3320a257 --- /dev/null +++ b/toml-config/src/config/parsers/modified_keysym.rs @@ -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 { + 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)), + } + } +} diff --git a/toml-config/src/config/parsers/output.rs b/toml-config/src/config/parsers/output.rs new file mode 100644 index 00000000..55010359 --- /dev/null +++ b/toml-config/src/config/parsers/output.rs @@ -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>, + ) -> ParseResult { + 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; + type Error = OutputParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array]; + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + 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>, + ) -> ParseResult { + 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]) + } +} diff --git a/toml-config/src/config/parsers/output_match.rs b/toml-config/src/config/parsers/output_match.rs new file mode 100644 index 00000000..d24cce37 --- /dev/null +++ b/toml-config/src/config/parsers/output_match.rs @@ -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]) -> ParseResult { + 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>, + ) -> ParseResult { + 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(), + }) + } +} diff --git a/toml-config/src/config/parsers/shortcuts.rs b/toml-config/src/config/parsers/shortcuts.rs new file mode 100644 index 00000000..c11d576f --- /dev/null +++ b/toml-config/src/config/parsers/shortcuts.rs @@ -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>, + ) -> ParseResult { + let mut used_keys = HashSet::>::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) + } +} diff --git a/toml-config/src/config/parsers/status.rs b/toml-config/src/config/parsers/status.rs new file mode 100644 index 00000000..68809cc2 --- /dev/null +++ b/toml-config/src/config/parsers/status.rs @@ -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>, + ) -> ParseResult { + 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, + }) + } +} diff --git a/toml-config/src/config/parsers/theme.rs b/toml-config/src/config/parsers/theme.rs new file mode 100644 index 00000000..4154cd0f --- /dev/null +++ b/toml-config/src/config/parsers/theme.rs @@ -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>, + ) -> ParseResult { + 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()), + }) + } +} diff --git a/toml-config/src/config/spanned.rs b/toml-config/src/config/spanned.rs new file mode 100644 index 00000000..a88282c7 --- /dev/null +++ b/toml-config/src/config/spanned.rs @@ -0,0 +1,63 @@ +use crate::{ + config::parser::{ParseResult, Parser}, + toml::{toml_span::Spanned, toml_value::Value}, +}; + +impl Spanned<&Value> { + pub fn parse(&self, parser: &mut P) -> ParseResult

{ + self.value.parse(self.span, parser) + } + + pub fn parse_map( + &self, + parser: &mut P, + ) -> Result<

::Value, Spanned> + where +

::Error: Into, + { + self.parse(parser).map_spanned_err(|e| e.into()) + } +} + +impl Spanned { + pub fn parse(&self, parser: &mut P) -> ParseResult

{ + self.as_ref().parse(parser) + } + + pub fn parse_map( + &self, + parser: &mut P, + ) -> Result<

::Value, Spanned> + where +

::Error: Into, + { + self.as_ref().parse_map(parser) + } +} + +pub trait SpannedErrorExt { + type T; + type E; + + fn map_spanned_err(self, f: F) -> Result> + where + F: FnOnce(Self::E) -> U; +} + +impl SpannedErrorExt for Result> { + type T = T; + type E = E; + + fn map_spanned_err(self, f: F) -> Result> + where + F: FnOnce(Self::E) -> U, + { + match self { + Ok(v) => Ok(v), + Err(e) => Err(Spanned { + span: e.span, + value: f(e.value), + }), + } + } +} diff --git a/toml-config/src/config/value.rs b/toml-config/src/config/value.rs new file mode 100644 index 00000000..0a80b0c6 --- /dev/null +++ b/toml-config/src/config/value.rs @@ -0,0 +1,17 @@ +use crate::{ + config::parser::{ParseResult, Parser}, + toml::{toml_span::Span, toml_value::Value}, +}; + +impl Value { + pub fn parse(&self, span: Span, parser: &mut P) -> ParseResult

{ + 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), + } + } +} diff --git a/toml-config/src/default-config.toml b/toml-config/src/default-config.toml new file mode 100644 index 00000000..1b65db26 --- /dev/null +++ b/toml-config/src/default-config.toml @@ -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" } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs new file mode 100644 index 00000000..743f8688 --- /dev/null +++ b/toml-config/src/lib.rs @@ -0,0 +1,824 @@ +#![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, + 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, + }, + Axis::{Horizontal, Vertical}, + Direction::{Down, Left, Right, Up}, + }, + std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc}, +}; + +fn default_seat() -> Seat { + get_seat("default") +} + +impl Action { + fn into_fn(self, state: &Rc) -> Box { + let s = state.persistent.seat; + match self { + Action::SimpleCommand { cmd } => match cmd { + SimpleCommand::FocusLeft => Box::new(move || s.focus(Left)), + SimpleCommand::FocusDown => Box::new(move || s.focus(Down)), + SimpleCommand::FocusUp => Box::new(move || s.focus(Up)), + SimpleCommand::FocusRight => Box::new(move || s.focus(Right)), + SimpleCommand::MoveLeft => Box::new(move || s.move_(Left)), + SimpleCommand::MoveDown => Box::new(move || s.move_(Down)), + SimpleCommand::MoveUp => Box::new(move || s.move_(Up)), + SimpleCommand::MoveRight => Box::new(move || s.move_(Right)), + SimpleCommand::SplitHorizontal => Box::new(move || s.create_split(Horizontal)), + SimpleCommand::SplitVertical => Box::new(move || s.create_split(Vertical)), + 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(); + } + } + }) + } + } + } +} + +fn apply_recursive_match<'a, U>( + type_name: &str, + list: &'a AHashMap, + 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, + drm_devices: AHashMap, + input_devices: AHashMap, + persistent: Rc, + keymaps: AHashMap, +} + +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, + shortcuts: impl IntoIterator, + ) { + 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) { + 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>, + default: Config, + seat: Seat, + binds: RefCell>, +} + +fn load_config(initial_load: bool, persistent: &Rc) { + 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); + } + } + 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); diff --git a/toml-config/src/toml.rs b/toml-config/src/toml.rs new file mode 100644 index 00000000..8c2ce876 --- /dev/null +++ b/toml-config/src/toml.rs @@ -0,0 +1,6 @@ +#[cfg(test)] +mod tests; +mod toml_lexer; +pub mod toml_parser; +pub mod toml_span; +pub mod toml_value; diff --git a/toml-config/src/toml/tests.rs b/toml-config/src/toml/tests.rs new file mode 100644 index 00000000..0645508a --- /dev/null +++ b/toml-config/src/toml/tests.rs @@ -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 { + 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) { + 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, 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::, + } + ); + } +} diff --git a/toml-config/src/toml/toml_lexer.rs b/toml-config/src/toml/toml_lexer.rs new file mode 100644 index 00000000..9ff85dc2 --- /dev/null +++ b/toml-config/src/toml/toml_lexer.rs @@ -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>>, + 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>> { + 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>> { + 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!())) + } +} diff --git a/toml-config/src/toml/toml_parser.rs b/toml-config/src/toml/toml_parser.rs new file mode 100644 index 00000000..878e6dce --- /dev/null +++ b/toml-config/src/toml/toml_parser.rs @@ -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); + fn redefinition(&self, err: Spanned, 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> { + 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, +} + +type Key = VecDeque>; + +impl<'a, 'b> Parser<'a, 'b> { + fn parse(mut self) -> Result, Spanned> { + self.parse_document() + } + + fn unexpected_eof(&self) -> Spanned { + 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> { + 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> { + match self.lexer.peek(value_context) { + Some(t) => Ok(t), + _ => Err(self.unexpected_eof()), + } + } + + fn parse_value(&mut self) -> Result, Spanned> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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, bool), Spanned> { + 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>, + ) -> Result<(), Spanned> { + 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>, + keys: &mut Key, + value: Spanned, + 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)>, Spanned> { + 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), (Spanned, Option)> { + 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> { + 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> { + 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; + } + } + } + } +} diff --git a/toml-config/src/toml/toml_span.rs b/toml-config/src/toml/toml_span.rs new file mode 100644 index 00000000..8b338654 --- /dev/null +++ b/toml-config/src/toml/toml_span.rs @@ -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 { + pub span: Span, + pub value: T, +} + +impl Spanned { + pub fn as_ref(&self) -> Spanned<&T> { + Spanned { + span: self.span, + value: &self.value, + } + } + + pub fn map U>(self, f: F) -> Spanned { + Spanned { + span: self.span, + value: f(self.value), + } + } + + pub fn into(self) -> Spanned + where + T: Into, + { + Spanned { + span: self.span, + value: self.value.into(), + } + } +} + +impl Debug for Spanned { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f)?; + write!(f, " @ {:?}", self.span) + } +} + +impl PartialEq for Spanned { + fn eq(&self, other: &Self) -> bool { + self.value.eq(&other.value) + } +} + +impl Hash for Spanned { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + +impl Borrow for Spanned { + fn borrow(&self) -> &str { + &self.value + } +} + +pub trait SpannedExt: Sized { + fn spanned(self, span: Span) -> Spanned; +} + +impl SpannedExt for T { + fn spanned(self, span: Span) -> Spanned { + Spanned { span, value: self } + } +} + +pub trait DespanExt: Sized { + type T; + + fn despan(self) -> Option; + fn despan_into(self) -> Option + where + Self::T: Into; +} + +impl DespanExt for Option> { + type T = T; + + fn despan(self) -> Option { + self.map(|v| v.value) + } + + fn despan_into(self) -> Option + where + Self::T: Into, + { + self.map(|v| v.value.into()) + } +} + +pub trait SpannedResultExt1: Sized { + type T; + type E; + + fn map_spanned U>(self, f: F) -> Result, Self::E>; +} + +impl SpannedResultExt1 for Result, E> { + type T = T; + type E = E; + + fn map_spanned U>(self, f: F) -> Result, Self::E> { + self.map(|v| v.map(f)) + } +} diff --git a/toml-config/src/toml/toml_value.rs b/toml-config/src/toml/toml_value.rs new file mode 100644 index 00000000..c93f942e --- /dev/null +++ b/toml-config/src/toml/toml_value.rs @@ -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>), + Table(IndexMap, Spanned>), +} + +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, + } + } +} diff --git a/toml-spec/Cargo.toml b/toml-spec/Cargo.toml new file mode 100644 index 00000000..09e0a7aa --- /dev/null +++ b/toml-spec/Cargo.toml @@ -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"] } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json new file mode 100644 index 00000000..665a8c5f --- /dev/null +++ b/toml-spec/spec/spec.generated.json @@ -0,0 +1,1062 @@ +{ + "$id": "jay_toml_schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/Config", + "$defs": { + "AccelProfile": { + "type": "string", + "description": "The acceleration profile to apply to an input device.\n\nSee the libinput documentation for more details.\n", + "enum": [ + "Flat", + "Adaptive" + ] + }, + "Action": { + "description": "", + "anyOf": [ + { + "description": "The value should be the name of a `simple` action. See the description of that\nvariant for more details.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", + "$ref": "#/$defs/SimpleActionName" + }, + { + "type": "array", + "description": "A list of actions to execute in sequence.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = [\n { type = \"exec\", exec = [\"notify-send\", \"exiting\"] },\n \"quit\",\n ]\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/Action" + } + }, + { + "description": "", + "anyOf": [ + { + "description": "A simple action that takes no arguments. These are usually written as plain\nstrings instead.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n alt-q = { type = \"simple\", cmd = \"quit\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "simple" + }, + "cmd": { + "description": "The simple action to execute.", + "$ref": "#/$defs/SimpleActionName" + } + }, + "required": [ + "type", + "cmd" + ] + }, + { + "description": "A list of actions to execute in sequence. These are usually written as plain\narrays instead.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n alt-q = { type = \"multi\", actions = [\"quit\", \"quit\"] }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n alt-q = [\"quit\", \"quit\"]\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "multi" + }, + "actions": { + "type": "array", + "description": "The actions to execute.", + "items": { + "description": "", + "$ref": "#/$defs/Action" + } + } + }, + "required": [ + "type", + "actions" + ] + }, + { + "description": "Executes a program.\n\n- Example:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ctrl-b = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "exec" + }, + "exec": { + "description": "The command to execute.", + "$ref": "#/$defs/Exec" + } + }, + "required": [ + "type", + "exec" + ] + }, + { + "description": "Switches to a virtual terminal.\n\n- Example:\n\n ```toml\n [shortcuts]\n ctrl-alt-F1 = { type = \"switch-to-vt\", num = 1 }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "switch-to-vt" + }, + "num": { + "type": "integer", + "description": "The VT number to switch to.\n", + "minimum": 1.0 + } + }, + "required": [ + "type", + "num" + ] + }, + { + "description": "Switches to a workspace.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"show-workspace\", name = \"1\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "show-workspace" + }, + "name": { + "type": "string", + "description": "The name of the workspace." + } + }, + "required": [ + "type", + "name" + ] + }, + { + "description": "Moves the currently focused window to a workspace.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-workspace\", name = \"1\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "move-to-workspace" + }, + "name": { + "type": "string", + "description": "The name of the workspace." + } + }, + "required": [ + "type", + "name" + ] + }, + { + "description": "Applies a configuration to connectors.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"configure-connector\", connector = { match.name = \"eDP-1\", enabled = false }\n alt-k = { type = \"configure-connector\", connector = { match.name = \"eDP-1\", enabled = true }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "configure-connector" + }, + "connector": { + "description": "The connector configuration.", + "$ref": "#/$defs/Connector" + } + }, + "required": [ + "type", + "connector" + ] + }, + { + "description": "Applies a configuration to input devices.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"configure-input\", input = { match.tag = \"mouse\", left-handed = true } }\n alt-r = { type = \"configure-input\", input = { match.tag = \"mouse\", left-handed = false } }\n\n [[inputs]]\n tag = \"mouse\"\n match.is-pointer = true\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "configure-input" + }, + "input": { + "description": "The input configuration.", + "$ref": "#/$defs/Input" + } + }, + "required": [ + "type", + "input" + ] + }, + { + "description": "Applies a configuration to input devices.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"configure-output\", output = { match.name = \"right\", transform = \"none\" } }\n alt-r = { type = \"configure-output\", output = { match.name = \"right\", transform = \"rotate-90\" } }\n\n [[outputs]]\n name = \"right\"\n match.serial-number = \"33K03894SL0\"\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "configure-output" + }, + "output": { + "description": "The output configuration.", + "$ref": "#/$defs/Output" + } + }, + "required": [ + "type", + "output" + ] + }, + { + "description": "Sets environment variables for all programs started afterwards.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"set-env\", env.GTK_THEME = \"Adwaita:dark\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-env" + }, + "env": { + "description": "The environment variables.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "" + } + } + }, + "required": [ + "type", + "env" + ] + }, + { + "description": "Unsets environment variables for all programs started afterwards.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"unset-env\", env = [\"Adwaita:dark\"] }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "unset-env" + }, + "env": { + "type": "array", + "description": "The environment variables.", + "items": { + "type": "string", + "description": "" + } + } + }, + "required": [ + "type", + "env" + ] + }, + { + "description": "Sets the keymap.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-keymap\", keymap.name = \"laptop\" }\n alt-k = { type = \"set-keymap\", keymap.name = \"external\" }\n\n [[keymaps]]\n name = \"laptop\"\n path = \"./laptop-keymap.xkb\"\n\n [[keymaps]]\n name = \"external\"\n path = \"./external-keymap.xkb\"\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-keymap" + }, + "keymap": { + "description": "The keymap.", + "$ref": "#/$defs/Keymap" + } + }, + "required": [ + "type", + "keymap" + ] + }, + { + "description": "Sets the status command.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-status\", status = { exec = \"i3status\" } }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-status" + }, + "status": { + "description": "The status setting.\n\nOmitting this causes the status to be reset to empty.\n", + "$ref": "#/$defs/Status" + } + }, + "required": [ + "type" + ] + }, + { + "description": "Sets the theme.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-theme\", theme.bg-color = \"#ff0000\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-theme" + }, + "theme": { + "description": "The theme.", + "$ref": "#/$defs/Theme" + } + }, + "required": [ + "type", + "theme" + ] + }, + { + "description": "Sets the log level of the compositor..\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-log-level\", level = \"debug\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-log-level" + }, + "theme": { + "description": "The log level.", + "$ref": "#/$defs/LogLevel" + } + }, + "required": [ + "type", + "theme" + ] + }, + { + "description": "Sets the graphics API used by new DRM devices.\n\nSetting this after the compositor has started usually has no effect.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-gfx-api\", api = \"Vulan\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-gfx-api" + }, + "api": { + "description": "The API.", + "$ref": "#/$defs/GfxApi" + } + }, + "required": [ + "type", + "api" + ] + }, + { + "description": "Configure whether the compositor attempts direct scanout of client surfaces.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"configure-direct-scanout\", enabled = false }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "configure-direct-scanout" + }, + "enabled": { + "type": "boolean", + "description": "Whether direct scanout is enabled." + } + }, + "required": [ + "type", + "enabled" + ] + }, + { + "description": "Applies a configuration to DRM devices.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"configure-drm-device\", dev = { match.name = \"integrated\", gfx-api = \"Vulkan\" } }\n\n [[drm-devices]]\n name = \"integrated\"\n match.syspath = \"/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0\"\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "configure-drm-device" + }, + "dev": { + "description": "The DRM device configuration.", + "$ref": "#/$defs/DrmDevice" + } + }, + "required": [ + "type", + "dev" + ] + }, + { + "description": "Sets the render device used for compositing.\n\nChanging this after the compositor has started might cause client windows to\nbecome invisible until they are resized.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-render-device\", dev.name = \"integrated\" }\n\n [[drm-devices]]\n name = \"integrated\"\n match.syspath = \"/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0\"\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "set-render-device" + }, + "dev": { + "description": "The rule to find the device.\n\nThe first matching device is used.\n", + "$ref": "#/$defs/DrmDeviceMatch" + } + }, + "required": [ + "type", + "dev" + ] + } + ] + } + ] + }, + "Color": { + "type": "string", + "description": "A color.\n\nThe format should be one of the following:\n\n- `#rgb`\n- `#rrggbb`\n- `#rgba`\n- `#rrggbba`\n" + }, + "Config": { + "description": "This is the top-level table.\n\n- Example:\n\n ```toml\n keymap = \"\"\"\n xkb_keymap {\n xkb_keycodes { include \"evdev+aliases(qwerty)\" };\n xkb_types { include \"complete\" };\n xkb_compat { include \"complete\" };\n xkb_symbols { include \"pc+us+inet(evdev)\" };\n };\n \"\"\"\n\n on-graphics-initialized = { type = \"exec\", exec = \"mako\" }\n\n [shortcuts]\n alt-h = \"focus-left\"\n alt-j = \"focus-down\"\n alt-k = \"focus-up\"\n alt-l = \"focus-right\"\n\n alt-shift-h = \"move-left\"\n alt-shift-j = \"move-down\"\n alt-shift-k = \"move-up\"\n alt-shift-l = \"move-right\"\n\n alt-d = \"split-horizontal\"\n alt-v = \"split-vertical\"\n\n alt-t = \"toggle-split\"\n alt-m = \"toggle-mono\"\n alt-u = \"toggle-fullscreen\"\n\n alt-f = \"focus-parent\"\n alt-shift-c = \"close\"\n alt-shift-f = \"toggle-floating\"\n Super_L = { type = \"exec\", exec = \"alacritty\" }\n alt-p = { type = \"exec\", exec = \"bemenu-run\" }\n alt-q = \"quit\"\n alt-shift-r = \"reload-config-toml\"\n\n ctrl-alt-F1 = { type = \"switch-to-vt\", num = 1 }\n ctrl-alt-F2 = { type = \"switch-to-vt\", num = 2 }\n # ...\n\n alt-F1 = { type = \"show-workspace\", name = \"1\" }\n alt-F2 = { type = \"show-workspace\", name = \"2\" }\n # ...\n\n alt-shift-F1 = { type = \"move-to-workspace\", name = \"1\" }\n alt-shift-F2 = { type = \"move-to-workspace\", name = \"2\" }\n # ...\n ```\n", + "type": "object", + "properties": { + "keymap": { + "description": "The keymap to use.\n\n- Example:\n\n ```toml\n keymap = \"\"\"\n xkb_keymap {\n xkb_keycodes { include \"evdev+aliases(qwerty)\" };\n xkb_types { include \"complete\" };\n xkb_compat { include \"complete\" };\n xkb_symbols { include \"pc+us+inet(evdev)\" };\n };\n \"\"\"\n ```\n", + "$ref": "#/$defs/Keymap" + }, + "shortcuts": { + "description": "The compositor shortcuts.\n\nThe keys should be in the following format:\n\n```\n(MOD-)*KEYSYM\n```\n\n`MOD` should be one of `shift`, `lock`, `ctrl`, `mod1`, `mod2`, `mod3`, `mod4`,\n`mod5`, `caps`, `alt`, `num`, or `logo`.\n\n`KEYSYM` should be the name of a keysym. The authorative location for these names\nis [1] with the `XKB_KEY_` prefix removed.\n\nThe keysym should be the unmodified keysym. E.g. `shift-q` not `shift-Q`.\n\n[1]: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", + "type": "object", + "additionalProperties": { + "description": "", + "$ref": "#/$defs/Action" + } + }, + "on-graphics-initialized": { + "description": "An action to execute when the graphics have been initialized for the first time.\n\nThis is a good place to start graphical applications.\n\n- Example:\n\n ```toml\n on-graphics-initialized = { type = \"exec\", exec = \"mako\" }\n ```\n", + "$ref": "#/$defs/Action" + }, + "status": { + "description": "The status program that will be used for the status text.\n\n- Example:\n\n ```toml\n [status]\n format = \"i3bar\"\n exec = \"i3status\"\n ```\n", + "$ref": "#/$defs/Status" + }, + "outputs": { + "type": "array", + "description": "An array of output configurations.\n\nThis can be used to configure outputs and create named outputs that can be\nreferred to in actions.\n\nThe configurations defined here will only be applied the first time matching\noutputs are connected to the compositor after the compositor has started.\nIf you want change the configuration afterwards, use `jay randr` or a\n`configure-output` action.\n\n- Example:\n\n ```toml\n [[outputs]]\n name = \"left\"\n match.serial-number = \"33K03894SL0\"\n x = 0\n y = 0\n\n [[outputs]]\n name = \"right\"\n match.serial-number = \"ETW1M02062SL0\"\n x = 1920\n y = 0\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/Output" + } + }, + "connectors": { + "type": "array", + "description": "An array of connector configurations.\n\nThis can be used to configure connectors.\n\nThe configurations defined here will only be applied when the connector is first\ndiscovered by the compositor. This usually never happens after the compositor has\nstarted unless you attach an external graphics card.\n\n- Example:\n\n ```toml\n [[connectors]]\n name = \"eDP-1\"\n enabled = false\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/Connector" + } + }, + "workspace-capture": { + "type": "boolean", + "description": "Configures whether newly created workspaces can be captured.\n\nThe default is `true`.\n" + }, + "env": { + "description": "Defines environment variables that will be set for all applications.\n\n- Example:\n\n ```toml\n [env]\n GTK_THEME = \"Adwaita:dark\"\n ```\n", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "" + } + }, + "on-startup": { + "description": "An action to execute as early as possible when the compositor starts.\n\nAt this point, graphics have not yet been initialized. You should not use this\nto start graphical applications. See `on-graphics-initialized`.\n\nThis setting has no effect during configuration reloads.\n", + "$ref": "#/$defs/Action" + }, + "keymaps": { + "type": "array", + "description": "Defines named keymaps.\n\nThese keymaps can be used to easily switch between keymaps for different\nkeyboards.\n\n- Example:\n\n ```toml\n keymap.name = \"laptop\"\n\n [shortcuts]\n alt-j = { type = \"set-keymap\", keymap.name = \"laptop\" }\n alt-k = { type = \"set-keymap\", keymap.name = \"external\" }\n\n [[keymaps]]\n name = \"laptop\"\n path = \"./laptop-keymap.xkb\"\n\n [[keymaps]]\n name = \"external\"\n path = \"./external-keymap.xkb\"\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/Keymap" + } + }, + "log-level": { + "description": "Sets the log level of the compositor.\n\nThis setting cannot be changed by re-loading the configuration. Use\n`jay set-log-level` instead.\n\n- Example:\n\n ```toml\n log-level = \"debug\"\n ```\n", + "$ref": "#/$defs/LogLevel" + }, + "theme": { + "description": "Sets the theme of the compositor.\n", + "$ref": "#/$defs/Theme" + }, + "gfx-api": { + "description": "Sets the graphics API used for newly discovered DRM devices.\n\nChanging this setting after the compositor has started usually has no effect\nunless you attach an external graphics card. Use `jay randr` to change the API\nused by individual devices at runtime.\n\n- Example:\n\n ```toml\n gfx-api = \"Vulkan\"\n ```\n", + "$ref": "#/$defs/GfxApi" + }, + "drm-devices": { + "type": "array", + "description": "Names and configures DRM devices.\n\nThese settings are only applied to devices discovered after the configuration\nhas been loaded. Therefore changing these settings usually has no effect at\nruntime unless you attach an external graphics card. You can use `jay randr` or\na `configure-drm-device` Action to change these settings at runtime.\n\n- Example:\n\n ```toml\n render-device.name = \"dedicated\"\n\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n\n [[drm-devices]]\n name = \"integrated\"\n match = { pci-vendor = 0x1002, pci-model = 0x164e }\n gfx-api = \"OpenGl\"\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/DrmDevice" + } + }, + "direct-scanout": { + "type": "boolean", + "description": "Configured whether the compositor attempts direct scanout.\n" + }, + "render-device": { + "description": "Selects the device to use for rendering in a system with multiple GPUs.\n\nThe first device that matches will be used.\n\n- Example:\n\n ```toml\n render-device.name = \"dedicated\"\n\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n ```\n", + "$ref": "#/$defs/DrmDeviceMatch" + }, + "inputs": { + "type": "array", + "description": "Names and configures input devices.\n\nThese settings are only applied to devices connected after the configuration\nhas been loaded. You can apply setting without re-connecting the device by using\n`jay input` or a `configure-input` Action.\n\n- Example:\n\n ```toml\n render-device.name = \"dedicated\"\n\n [[inputs]]\n match.is-pointer = true\n left-handed = true\n transform-matrix = [[0.35, 0], [0, 0.35]]\n tap-enabled = true\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/Input" + } + }, + "on-idle": { + "description": "An action to execute when the compositor becomes idle.\n\n- Example:\n\n ```toml\n on-idle = { type = \"exec\", exec = \"lock\" }\n ```\n", + "$ref": "#/$defs/Action" + } + }, + "required": [] + }, + "Connector": { + "description": "Describes configuration to apply to a connector.\n\n- Example: To disable the built-in display of a laptop:\n\n ```toml\n [[connectors]]\n match.name = \"eDP-1\"\n enabled = false\n ```\n", + "type": "object", + "properties": { + "match": { + "description": "The rule by which the connectors to modify are selected.\n", + "$ref": "#/$defs/ConnectorMatch" + }, + "enabled": { + "type": "boolean", + "description": "If specified, enables or disables the connector.\n" + } + }, + "required": [ + "match" + ] + }, + "ConnectorMatch": { + "description": "Rules to match one of the connectors used by the compositor.\n", + "anyOf": [ + { + "type": "array", + "description": "This rule matches if any of the rules in the array match.\n", + "items": { + "description": "", + "$ref": "#/$defs/ConnectorMatch" + } + }, + { + "description": "Describes a rule that matches a subset of connectors.\n\nThis rule matches if all of the specified fields match.\n\n- Example:\n\n ```toml\n [[connectors]]\n match.name = \"DP-1\"\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the connector.\n\nThese values are not necessarily stable. You can find out the value by running\n`jay randr`.\n" + } + }, + "required": [] + } + ] + }, + "DrmDevice": { + "description": "Describes configuration to apply to a DRM device (graphics card).\n\n- Example: To disable direct scanout on a device:\n\n ```toml\n [[drm-devices]]\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n direct-scanout = false\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Assigns a name to the rule in the `match` field.\n\nThis only has an effect when used in the top-level `drm-devices` array.\n" + }, + "match": { + "description": "The rule by which the DRM devices to modify are selected.\n", + "$ref": "#/$defs/DrmDeviceMatch" + }, + "direct-scanout": { + "type": "boolean", + "description": "If specified, enables or disables direct scanout on this device.\n" + }, + "gfx-api": { + "description": "If specified, sets the graphics API to use for this device.\n", + "$ref": "#/$defs/GfxApi" + } + }, + "required": [ + "match" + ] + }, + "DrmDeviceMatch": { + "description": "Rules to match one of the DRM devices (graphics cards) used by the compositor.\n", + "anyOf": [ + { + "type": "array", + "description": "This rule matches if any of the rules in the array match.\n", + "items": { + "description": "", + "$ref": "#/$defs/DrmDeviceMatch" + } + }, + { + "description": "Describes a rule that matches a subset of DRM devices.\n\nThis rule matches if all of the specified fields match.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of another DrmDeviceMatch rule.\n\nFor this rule to match, the referenced rule must match. The name of the rule\nshould have been defined in the top-level `drm-devices` array.\n\nThis can be used to easily refer to DRM devices.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-v = { type = \"configure-drm-device\", dev = { match.name = \"dedicated\", gfx-api = \"Vulkan\" } }\n alt-o = { type = \"configure-drm-device\", dev = { match.name = \"dedicated\", gfx-api = \"OpenGl\" } }\n\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n ```\n" + }, + "syspath": { + "type": "string", + "description": "The syspath of the device.\n\nThis is useful if you have multiple copies of the same device installed so that\nthe PCI numbers are not unique.\n\nThe values are usually stable unless you re-configure your hardware.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"integrated\"\n match.syspath = \"/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0\"\n ```\n" + }, + "devnode": { + "type": "string", + "description": "The devnode of the device.\n\nThe values are usually not-stable across PC restarts.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"integrated\"\n match.devnode = \"/dev/dri/card0\"\n ```\n" + }, + "vendor": { + "type": "string", + "description": "The name of the vendor.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"integrated\"\n match.vendor = \"Advanced Micro Devices, Inc. [AMD/ATI]\"\n ```\n" + }, + "model": { + "type": "string", + "description": "The name of the model.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"integrated\"\n match.vendor = \"Raphael\"\n ```\n" + }, + "pci-vendor": { + "type": "integer", + "description": "The PCI number of the vendor.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"integrated\"\n match.pci-vendor = 0x1002\n ```\n" + }, + "pci-model": { + "type": "integer", + "description": "The PCI number of the model.\n\n- Example:\n\n ```toml\n [[drm-devices]]\n name = \"integrated\"\n match.pci-model = 0x164e\n ```\n" + } + }, + "required": [] + } + ] + }, + "Exec": { + "description": "Describes how to execute a program.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n\n- Example 3:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n", + "anyOf": [ + { + "type": "string", + "description": "The name of the executable to execute.\n\n- Example:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ```\n" + }, + { + "type": "array", + "description": "The name and arguments of the executable to execute.\n\n- Example:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n", + "items": { + "type": "string", + "description": "" + } + }, + { + "description": "The name, arguments, and environment variables of the executable to execute.\n\n- Example:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n", + "type": "object", + "properties": { + "prog": { + "type": "string", + "description": "The name of the executable." + }, + "args": { + "type": "array", + "description": "The arguments to pass to the executable.", + "items": { + "type": "string", + "description": "" + } + }, + "env": { + "description": "The environment variables to pass to the executable.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "" + } + } + }, + "required": [ + "prog" + ] + } + ] + }, + "GfxApi": { + "type": "string", + "description": "A graphics API used for rendering.", + "enum": [ + "OpenGl", + "Vulkan" + ] + }, + "Input": { + "description": "Describes configuration to apply to an input device.\n\n- Example: To make mice left handed:\n\n ```toml\n [[inputs]]\n match.is-pointer = true\n left-handed = true\n ```\n", + "type": "object", + "properties": { + "tag": { + "type": "string", + "description": "Assigns a name to the rule in the `match` field.\n\nThis only has an effect when used in the top-level `inputs` array.\n" + }, + "match": { + "description": "The rule by which the input devices to modify are selected.\n", + "$ref": "#/$defs/InputMatch" + }, + "accel-profile": { + "description": "The acceleration profile to use.\n\nSee the libinput documentation for more details.\n", + "$ref": "#/$defs/AccelProfile" + }, + "accel-speed": { + "type": "number", + "description": "The acceleration speed to use.\n\nValues should be in the range -1 to 1.\n\nSee the libinput documentation for more details.\n" + }, + "tap-enabled": { + "type": "boolean", + "description": "Whether tap is enabled for this device.\n\nSee the libinput documentation for more details.\n" + }, + "tap-drag-enabled": { + "type": "boolean", + "description": "Whether tap drag is enabled for this device.\n\nSee the libinput documentation for more details.\n" + }, + "tap-drag-lock-enabled": { + "type": "boolean", + "description": "Whether tap drag lock is enabled for this device.\n\nSee the libinput documentation for more details.\n" + }, + "left-handed": { + "type": "boolean", + "description": "Whether the device is left handed.\n\nSee the libinput documentation for more details.\n" + }, + "natural-scrolling": { + "type": "boolean", + "description": "Whether the device uses natural scrolling.\n\nSee the libinput documentation for more details.\n" + }, + "px-per-wheel-scroll": { + "type": "boolean", + "description": "The number of pixels to scroll for each scroll wheel dedent.\n" + }, + "transform-matrix": { + "type": "array", + "description": "A transformation matrix to apply to each motion event of this device.\nThe matrix should be 2x2.\n\n- Example: To slow down the mouse to 35% of normal speed:\n\n ```toml\n [[inputs]]\n match.is-pointer = true\n transform-matrix = [[0.35, 0], [0, 0.35]]\n ```\n", + "items": { + "type": "array", + "description": "", + "items": { + "type": "number", + "description": "" + } + } + } + }, + "required": [ + "match" + ] + }, + "InputMatch": { + "description": "Rules to match one of the input devices used by the compositor.\n", + "anyOf": [ + { + "type": "array", + "description": "This rule matches if any of the rules in the array match.\n", + "items": { + "description": "", + "$ref": "#/$defs/InputMatch" + } + }, + { + "description": "Describes a rule that matches a subset of input devices.\n\nThis rule matches if all of the specified fields match.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-pointer = true\n left-handed = true\n ```\n", + "type": "object", + "properties": { + "tag": { + "type": "string", + "description": "The tag of another InputMatch rule.\n\nFor this rule to match, the referenced rule must match. The name of the rule\nshould have been defined in the top-level `inputs` array.\n\nThis can be used to easily refer to input devices.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"configure-input\", input = { match.tag = \"mouse\", left-handed = true } }\n alt-r = { type = \"configure-input\", input = { match.tag = \"mouse\", left-handed = false } }\n\n [[inputs]]\n tag = \"mouse\"\n match.is-pointer = true\n ```\n" + }, + "name": { + "type": "string", + "description": "The libinput name of the device.\n\nYou can find out the name of the devices by running `jay input`.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.name = \"Logitech G300s Optical Gaming Mouse\"\n left-handed = true\n ```\n" + }, + "syspath": { + "type": "string", + "description": "The syspath of the device.\n\nThis is useful if you have multiple copies of the same device installed so that\nthe name is not unique.\n\nThe values are usually stable unless you re-configure your hardware.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.syspath = \"/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.2/5-1.1.2:1.0\"\n left-handed = true\n ```\n" + }, + "devnode": { + "type": "string", + "description": "The devnode of the device.\n\nThe values are usually not-stable across PC restarts.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.devnode = \"/dev/input/event4\"\n left-handed = true\n ```\n" + }, + "is-keyboard": { + "type": "boolean", + "description": "Whether the devices has been identified as a keyboard.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-keyboard = false\n left-handed = true\n ```\n" + }, + "is-pointer": { + "type": "boolean", + "description": "Whether the devices has been identified as a pointer.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-pointer = false\n left-handed = true\n ```\n" + }, + "is-touch": { + "type": "boolean", + "description": "Whether the devices has been identified as a touch device.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-touch = true\n tap-enabled = true\n ```\n" + }, + "is-tablet-tool": { + "type": "boolean", + "description": "Whether the devices has been identified as a tablet tool.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-tablet-tool = true\n tap-enabled = true\n ```\n" + }, + "is-tablet-pad": { + "type": "boolean", + "description": "Whether the devices has been identified as a tablet pad.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-tablet-tool = true\n tap-enabled = true\n ```\n" + }, + "is-gesture": { + "type": "boolean", + "description": "Whether the devices has been identified as a switch.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.is-switch = true\n ```\n" + } + }, + "required": [] + } + ] + }, + "Keymap": { + "description": "A keymap.\n", + "anyOf": [ + { + "type": "string", + "description": "Defines a keymap by its XKB representation.\n\n- Example:\n\n ```toml\n keymap = \"\"\"\n xkb_keymap {\n xkb_keycodes { include \"evdev+aliases(qwerty)\" };\n xkb_types { include \"complete\" };\n xkb_compat { include \"complete\" };\n xkb_symbols { include \"pc+us+inet(evdev)\" };\n };\n \"\"\"\n ```\n" + }, + { + "description": "Defines or references a keymap.\n\n- Example:\n\n ```toml\n keymap.name = \"my-keymap\"\n\n [[keymaps]]\n name = \"my-keymap\"\n path = \"./my-keymap.xkb\"\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Defines a keymap name or references a defined keymap.\n\nIf the value is set in the top-level `keymaps` array, it defines a named\nkeymap.\n\nOtherwise it references a named keymap that should have been defined in the\n`keymaps` array.\n" + }, + "map": { + "type": "string", + "description": "Defines a keymap by its XKB representation.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of `map`\nand `path` has to be defined.\n" + }, + "path": { + "type": "string", + "description": "Loads a keymap's XKB representation from a file.\n\nIf the path is relative, it will be interpreted relative to the Jay config\ndirectory.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of `map`\nand `path` has to be defined.\n" + } + }, + "required": [] + } + ] + }, + "LogLevel": { + "type": "string", + "description": "A log level.", + "enum": [ + "trace", + "debug", + "info", + "warn", + "error" + ] + }, + "MessageFormat": { + "type": "string", + "description": "A message format used by status programs.", + "enum": [ + "plain", + "pango", + "i3bar" + ] + }, + "Mode": { + "description": "The mode of a display.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n mode = { width = 1920, height = 1080, refresh-rate = 59.94 }\n ```\n", + "type": "object", + "properties": { + "width": { + "type": "integer", + "description": "The width of the mode." + }, + "height": { + "type": "integer", + "description": "The height of the mode." + }, + "refresh-rate": { + "type": "number", + "description": "The refresh rate of the mode in HZ." + } + }, + "required": [ + "width", + "height" + ] + }, + "Output": { + "description": "Describes configuration to apply to an output.\n\n- Example: To set the scale of an output.\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n scale = 1.25\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Assigns a name to the rule in the `match` field.\n\nThis only has an effect when used in the top-level `outputs` array.\n" + }, + "match": { + "description": "The rule by which the outputs to modify are selected.\n", + "$ref": "#/$defs/OutputMatch" + }, + "x": { + "type": "integer", + "description": "The x coordinate of the output in compositor space.\n", + "minimum": 0.0 + }, + "y": { + "type": "integer", + "description": "The y coordinate of the output in compositor space.\n", + "minimum": 0.0 + }, + "scale": { + "type": "number", + "description": "The scale of the output.\n", + "exclusiveMinimum": 0.0 + }, + "transform": { + "description": "The transform of the output.\n", + "$ref": "#/$defs/Transform" + }, + "mode": { + "description": "The mode of the output.\n\nIf the refresh rate is not specified, the first mode with the specified width and\nheight is used.\n", + "$ref": "#/$defs/Mode" + } + }, + "required": [ + "match" + ] + }, + "OutputMatch": { + "description": "Rules to match one of the outputs used by the compositor.\n", + "anyOf": [ + { + "type": "array", + "description": "This rule matches if any of the rules in the array match.\n", + "items": { + "description": "", + "$ref": "#/$defs/OutputMatch" + } + }, + { + "description": "Describes a rule that matches a subset of outputs.\n\nThis rule matches if all of the specified fields match.\n\n- Example:\n\n ```toml\n [[outputs]]\n name = \"right\"\n match.serial-number = \"33K03894SL0\"\n x = 1920\n y = 0\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of another OutputMatch rule.\n\nFor this rule to match, the referenced rule must match. The name of the rule\nshould have been defined in the top-level `outputs` array.\n\nThis can be used to easily refer to outputs.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"configure-output\", output = { match.name = \"right\", transform = \"none\" } }\n alt-r = { type = \"configure-output\", output = { match.name = \"right\", transform = \"rotate-90\" } }\n\n [[outputs]]\n name = \"right\"\n match.serial-number = \"33K03894SL0\"\n ```\n" + }, + "connector": { + "type": "string", + "description": "The name of the connector the output is connected to.\n\nYou can find out the name of the connector by running `jay randr`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.connector = \"DP-1\"\n scale = 1.25\n ```\n" + }, + "serial-number": { + "type": "string", + "description": "The serial number of the output.\n\nYou can find out the serial number by running `jay randr`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n scale = 1.25\n ```\n" + }, + "manufacturer": { + "type": "string", + "description": "The manufacturer of the output.\n\nYou can find out the manufacturer by running `jay randr`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.manufacturer = \"BNQ\"\n scale = 1.25\n ```\n" + }, + "model": { + "type": "string", + "description": "The model of the output.\n\nYou can find out the model by running `jay randr`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.model = \"BenQ GW2480\"\n scale = 1.25\n ```\n" + } + }, + "required": [] + } + ] + }, + "SimpleActionName": { + "type": "string", + "description": "The name of a `simple` Action.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", + "enum": [ + "focus-left", + "focus-down", + "focus-up", + "focus-right", + "move-left", + "move-down", + "move-up", + "move-right", + "move-right", + "split-horizontal", + "split-vertical", + "toggle-split", + "toggle-mono", + "toggle-fullscreen", + "focus-parent", + "close", + "disable-pointer-constraint", + "toggle-floating", + "quit", + "reload-config-toml", + "reload-config-to", + "none" + ] + }, + "Status": { + "description": "The configuration of a status program whose output will be shown in the bar.\n\n- Example:\n\n ```toml\n [status]\n format = \"i3bar\"\n exec = \"i3status\"\n ```\n", + "type": "object", + "properties": { + "format": { + "description": "The format used by the program.", + "$ref": "#/$defs/MessageFormat" + }, + "exec": { + "description": "The program that will emit the status messages.", + "$ref": "#/$defs/Exec" + }, + "i3bar-separator": { + "type": "string", + "description": "The separator to be used between i3bar components.\n\nThe default is ` | `.\n" + } + }, + "required": [ + "exec" + ] + }, + "Theme": { + "description": "The theme of the compositor.\n", + "type": "object", + "properties": { + "attention-requested-bg-color": { + "description": "The background color of title that have requested attention.", + "$ref": "#/$defs/Color" + }, + "bg-color": { + "description": "The background color of the desktop.", + "$ref": "#/$defs/Color" + }, + "bar-bg-color": { + "description": "The background color of the bar.", + "$ref": "#/$defs/Color" + }, + "bar-status-text-color": { + "description": "The color of the status text in the bar.", + "$ref": "#/$defs/Color" + }, + "border-color": { + "description": "The color of the borders between windows.", + "$ref": "#/$defs/Color" + }, + "captured-focused-title-bg-color": { + "description": "The background color of focused titles that are being recorded.", + "$ref": "#/$defs/Color" + }, + "captured-unfocused-title-bg-color": { + "description": "The background color of unfocused titles that are being recorded.", + "$ref": "#/$defs/Color" + }, + "focused-inactive-title-bg-color": { + "description": "The background color of focused titles that are inactive.", + "$ref": "#/$defs/Color" + }, + "focused-inactive-title-text-color": { + "description": "The text color of focused titles that are inactive.", + "$ref": "#/$defs/Color" + }, + "focused-title-bg-color": { + "description": "The background color of focused titles.", + "$ref": "#/$defs/Color" + }, + "focused-title-text-color": { + "description": "The text color of focused titles.", + "$ref": "#/$defs/Color" + }, + "separator-color": { + "description": "The color of the separator between titles and window content.", + "$ref": "#/$defs/Color" + }, + "unfocused-title-bg-color": { + "description": "The background color of unfocused titles.", + "$ref": "#/$defs/Color" + }, + "unfocused-title-text-color": { + "description": "The text color of unfocused titles.", + "$ref": "#/$defs/Color" + }, + "border-width": { + "type": "integer", + "description": "The width of borders between windows.", + "minimum": 0.0 + }, + "title-height": { + "type": "integer", + "description": "The height of tabs.", + "minimum": 0.0 + }, + "font": { + "type": "string", + "description": "The name of the font to use." + } + }, + "required": [] + }, + "Transform": { + "type": "string", + "description": "An output transformation.", + "enum": [ + "none", + "rotate-90", + "rotate-180", + "rotate-270", + "flip", + "flip-rotate-90", + "flip-rotate-180", + "flip-rotate-270" + ] + } + } +} \ No newline at end of file diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md new file mode 100644 index 00000000..4cb6d5b7 --- /dev/null +++ b/toml-spec/spec/spec.generated.md @@ -0,0 +1,2222 @@ +# 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 + + +### `AccelProfile` + +The acceleration profile to apply to an input device. + +See the libinput documentation for more details. + +Values of this type should be strings. + +The string should have one of the following values: + +- `Flat`: + + The flat profile. + +- `Adaptive`: + + The adaptive profile. + + + + +### `Action` + + + +Values of this type should have one of the following forms: + +#### A string + +The value should be the name of a `simple` action. See the description of that +variant for more details. + +- Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + +The value should be a [SimpleActionName](#types-SimpleActionName). + +#### An array + +A list of actions to execute in sequence. + +- Example: + + ```toml + [shortcuts] + alt-q = [ + { type = "exec", exec = ["notify-send", "exiting"] }, + "quit", + ] + ``` + +Each element of this array should be a [Action](#types-Action). + +#### A table + + + +This table is a tagged union. The variant is determined by the `type` field. It takes one of the following values: + +- `simple`: + + A simple action that takes no arguments. These are usually written as plain + strings instead. + + - Example 1: + + ```toml + [shortcuts] + alt-q = { type = "simple", cmd = "quit" } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + + The table has the following fields: + + - `cmd` (required): + + The simple action to execute. + + The value of this field should be a [SimpleActionName](#types-SimpleActionName). + +- `multi`: + + A list of actions to execute in sequence. These are usually written as plain + arrays instead. + + - Example 1: + + ```toml + [shortcuts] + alt-q = { type = "multi", actions = ["quit", "quit"] } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-q = ["quit", "quit"] + ``` + + The table has the following fields: + + - `actions` (required): + + The actions to execute. + + The value of this field should be an array of [Actions](#types-Action). + +- `exec`: + + Executes a program. + + - Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = "alacritty" } + ctrl-b = { type = "exec", exec = ["notify-send", "hello world"] } + ``` + + The table has the following fields: + + - `exec` (required): + + The command to execute. + + The value of this field should be a [Exec](#types-Exec). + +- `switch-to-vt`: + + Switches to a virtual terminal. + + - Example: + + ```toml + [shortcuts] + ctrl-alt-F1 = { type = "switch-to-vt", num = 1 } + ``` + + The table has the following fields: + + - `num` (required): + + The VT number to switch to. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 1. + +- `show-workspace`: + + Switches to a workspace. + + - Example: + + ```toml + [shortcuts] + alt-F1 = { type = "show-workspace", name = "1" } + ``` + + The table has the following fields: + + - `name` (required): + + The name of the workspace. + + The value of this field should be a string. + +- `move-to-workspace`: + + Moves the currently focused window to a workspace. + + - Example: + + ```toml + [shortcuts] + alt-F1 = { type = "move-to-workspace", name = "1" } + ``` + + The table has the following fields: + + - `name` (required): + + The name of the workspace. + + The value of this field should be a string. + +- `configure-connector`: + + Applies a configuration to connectors. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "configure-connector", connector = { match.name = "eDP-1", enabled = false } + alt-k = { type = "configure-connector", connector = { match.name = "eDP-1", enabled = true } + ``` + + The table has the following fields: + + - `connector` (required): + + The connector configuration. + + The value of this field should be a [Connector](#types-Connector). + +- `configure-input`: + + Applies a configuration to input devices. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-input", input = { match.tag = "mouse", left-handed = true } } + alt-r = { type = "configure-input", input = { match.tag = "mouse", left-handed = false } } + + [[inputs]] + tag = "mouse" + match.is-pointer = true + ``` + + The table has the following fields: + + - `input` (required): + + The input configuration. + + The value of this field should be a [Input](#types-Input). + +- `configure-output`: + + Applies a configuration to input devices. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-output", output = { match.name = "right", transform = "none" } } + alt-r = { type = "configure-output", output = { match.name = "right", transform = "rotate-90" } } + + [[outputs]] + name = "right" + match.serial-number = "33K03894SL0" + ``` + + The table has the following fields: + + - `output` (required): + + The output configuration. + + The value of this field should be a [Output](#types-Output). + +- `set-env`: + + Sets environment variables for all programs started afterwards. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "set-env", env.GTK_THEME = "Adwaita:dark" } + ``` + + The table has the following fields: + + - `env` (required): + + The environment variables. + + The value of this field should be a table whose values are strings. + +- `unset-env`: + + Unsets environment variables for all programs started afterwards. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "unset-env", env = ["Adwaita:dark"] } + ``` + + The table has the following fields: + + - `env` (required): + + The environment variables. + + The value of this field should be an array of strings. + +- `set-keymap`: + + Sets the keymap. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-keymap", keymap.name = "laptop" } + alt-k = { type = "set-keymap", keymap.name = "external" } + + [[keymaps]] + name = "laptop" + path = "./laptop-keymap.xkb" + + [[keymaps]] + name = "external" + path = "./external-keymap.xkb" + ``` + + The table has the following fields: + + - `keymap` (required): + + The keymap. + + The value of this field should be a [Keymap](#types-Keymap). + +- `set-status`: + + Sets the status command. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-status", status = { exec = "i3status" } } + ``` + + The table has the following fields: + + - `status` (optional): + + The status setting. + + Omitting this causes the status to be reset to empty. + + The value of this field should be a [Status](#types-Status). + +- `set-theme`: + + Sets the theme. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-theme", theme.bg-color = "#ff0000" } + ``` + + The table has the following fields: + + - `theme` (required): + + The theme. + + The value of this field should be a [Theme](#types-Theme). + +- `set-log-level`: + + Sets the log level of the compositor.. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-log-level", level = "debug" } + ``` + + The table has the following fields: + + - `theme` (required): + + The log level. + + The value of this field should be a [LogLevel](#types-LogLevel). + +- `set-gfx-api`: + + Sets the graphics API used by new DRM devices. + + Setting this after the compositor has started usually has no effect. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-gfx-api", api = "Vulan" } + ``` + + The table has the following fields: + + - `api` (required): + + The API. + + The value of this field should be a [GfxApi](#types-GfxApi). + +- `configure-direct-scanout`: + + Configure whether the compositor attempts direct scanout of client surfaces. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "configure-direct-scanout", enabled = false } + ``` + + The table has the following fields: + + - `enabled` (required): + + Whether direct scanout is enabled. + + The value of this field should be a boolean. + +- `configure-drm-device`: + + Applies a configuration to DRM devices. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "configure-drm-device", dev = { match.name = "integrated", gfx-api = "Vulkan" } } + + [[drm-devices]] + name = "integrated" + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" + ``` + + The table has the following fields: + + - `dev` (required): + + The DRM device configuration. + + The value of this field should be a [DrmDevice](#types-DrmDevice). + +- `set-render-device`: + + Sets the render device used for compositing. + + Changing this after the compositor has started might cause client windows to + become invisible until they are resized. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-render-device", dev.name = "integrated" } + + [[drm-devices]] + name = "integrated" + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" + ``` + + The table has the following fields: + + - `dev` (required): + + The rule to find the device. + + The first matching device is used. + + The value of this field should be a [DrmDeviceMatch](#types-DrmDeviceMatch). + + + +### `Color` + +A color. + +The format should be one of the following: + +- `#rgb` +- `#rrggbb` +- `#rgba` +- `#rrggbba` + +Values of this type should be strings. + + + +### `Config` + +This is the top-level table. + +- Example: + + ```toml + 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 } + # ... + + alt-F1 = { type = "show-workspace", name = "1" } + alt-F2 = { type = "show-workspace", name = "2" } + # ... + + alt-shift-F1 = { type = "move-to-workspace", name = "1" } + alt-shift-F2 = { type = "move-to-workspace", name = "2" } + # ... + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `keymap` (optional): + + The keymap to use. + + - Example: + + ```toml + keymap = """ + xkb_keymap { + xkb_keycodes { include "evdev+aliases(qwerty)" }; + xkb_types { include "complete" }; + xkb_compat { include "complete" }; + xkb_symbols { include "pc+us+inet(evdev)" }; + }; + """ + ``` + + The value of this field should be a [Keymap](#types-Keymap). + +- `shortcuts` (optional): + + The compositor shortcuts. + + The keys should be in the following format: + + ``` + (MOD-)*KEYSYM + ``` + + `MOD` should be one of `shift`, `lock`, `ctrl`, `mod1`, `mod2`, `mod3`, `mod4`, + `mod5`, `caps`, `alt`, `num`, or `logo`. + + `KEYSYM` should be the name of a keysym. The authorative location for these names + is [1] with the `XKB_KEY_` prefix removed. + + The keysym should be the unmodified keysym. E.g. `shift-q` not `shift-Q`. + + [1]: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h + + - Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + + The value of this field should be a table whose values are [Actions](#types-Action). + +- `on-graphics-initialized` (optional): + + An action to execute when the graphics have been initialized for the first time. + + This is a good place to start graphical applications. + + - Example: + + ```toml + on-graphics-initialized = { type = "exec", exec = "mako" } + ``` + + The value of this field should be a [Action](#types-Action). + +- `status` (optional): + + The status program that will be used for the status text. + + - Example: + + ```toml + [status] + format = "i3bar" + exec = "i3status" + ``` + + The value of this field should be a [Status](#types-Status). + +- `outputs` (optional): + + An array of output configurations. + + This can be used to configure outputs and create named outputs that can be + referred to in actions. + + The configurations defined here will only be applied the first time matching + outputs are connected to the compositor after the compositor has started. + If you want change the configuration afterwards, use `jay randr` or a + `configure-output` action. + + - Example: + + ```toml + [[outputs]] + name = "left" + match.serial-number = "33K03894SL0" + x = 0 + y = 0 + + [[outputs]] + name = "right" + match.serial-number = "ETW1M02062SL0" + x = 1920 + y = 0 + ``` + + The value of this field should be an array of [Outputs](#types-Output). + +- `connectors` (optional): + + An array of connector configurations. + + This can be used to configure connectors. + + The configurations defined here will only be applied when the connector is first + discovered by the compositor. This usually never happens after the compositor has + started unless you attach an external graphics card. + + - Example: + + ```toml + [[connectors]] + name = "eDP-1" + enabled = false + ``` + + The value of this field should be an array of [Connectors](#types-Connector). + +- `workspace-capture` (optional): + + Configures whether newly created workspaces can be captured. + + The default is `true`. + + The value of this field should be a boolean. + +- `env` (optional): + + Defines environment variables that will be set for all applications. + + - Example: + + ```toml + [env] + GTK_THEME = "Adwaita:dark" + ``` + + The value of this field should be a table whose values are strings. + +- `on-startup` (optional): + + An action to execute as early as possible when the compositor starts. + + At this point, graphics have not yet been initialized. You should not use this + to start graphical applications. See `on-graphics-initialized`. + + This setting has no effect during configuration reloads. + + The value of this field should be a [Action](#types-Action). + +- `keymaps` (optional): + + Defines named keymaps. + + These keymaps can be used to easily switch between keymaps for different + keyboards. + + - Example: + + ```toml + keymap.name = "laptop" + + [shortcuts] + alt-j = { type = "set-keymap", keymap.name = "laptop" } + alt-k = { type = "set-keymap", keymap.name = "external" } + + [[keymaps]] + name = "laptop" + path = "./laptop-keymap.xkb" + + [[keymaps]] + name = "external" + path = "./external-keymap.xkb" + ``` + + The value of this field should be an array of [Keymaps](#types-Keymap). + +- `log-level` (optional): + + Sets the log level of the compositor. + + This setting cannot be changed by re-loading the configuration. Use + `jay set-log-level` instead. + + - Example: + + ```toml + log-level = "debug" + ``` + + The value of this field should be a [LogLevel](#types-LogLevel). + +- `theme` (optional): + + Sets the theme of the compositor. + + The value of this field should be a [Theme](#types-Theme). + +- `gfx-api` (optional): + + Sets the graphics API used for newly discovered DRM devices. + + Changing this setting after the compositor has started usually has no effect + unless you attach an external graphics card. Use `jay randr` to change the API + used by individual devices at runtime. + + - Example: + + ```toml + gfx-api = "Vulkan" + ``` + + The value of this field should be a [GfxApi](#types-GfxApi). + +- `drm-devices` (optional): + + Names and configures DRM devices. + + These settings are only applied to devices discovered after the configuration + has been loaded. Therefore changing these settings usually has no effect at + runtime unless you attach an external graphics card. You can use `jay randr` or + a `configure-drm-device` Action to change these settings at runtime. + + - Example: + + ```toml + render-device.name = "dedicated" + + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + + [[drm-devices]] + name = "integrated" + match = { pci-vendor = 0x1002, pci-model = 0x164e } + gfx-api = "OpenGl" + ``` + + The value of this field should be an array of [DrmDevices](#types-DrmDevice). + +- `direct-scanout` (optional): + + Configured whether the compositor attempts direct scanout. + + The value of this field should be a boolean. + +- `render-device` (optional): + + Selects the device to use for rendering in a system with multiple GPUs. + + The first device that matches will be used. + + - Example: + + ```toml + render-device.name = "dedicated" + + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + ``` + + The value of this field should be a [DrmDeviceMatch](#types-DrmDeviceMatch). + +- `inputs` (optional): + + Names and configures input devices. + + These settings are only applied to devices connected after the configuration + has been loaded. You can apply setting without re-connecting the device by using + `jay input` or a `configure-input` Action. + + - Example: + + ```toml + render-device.name = "dedicated" + + [[inputs]] + match.is-pointer = true + left-handed = true + transform-matrix = [[0.35, 0], [0, 0.35]] + tap-enabled = true + ``` + + The value of this field should be an array of [Inputs](#types-Input). + +- `on-idle` (optional): + + An action to execute when the compositor becomes idle. + + - Example: + + ```toml + on-idle = { type = "exec", exec = "lock" } + ``` + + The value of this field should be a [Action](#types-Action). + + + +### `Connector` + +Describes configuration to apply to a connector. + +- Example: To disable the built-in display of a laptop: + + ```toml + [[connectors]] + match.name = "eDP-1" + enabled = false + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `match` (required): + + The rule by which the connectors to modify are selected. + + The value of this field should be a [ConnectorMatch](#types-ConnectorMatch). + +- `enabled` (optional): + + If specified, enables or disables the connector. + + The value of this field should be a boolean. + + + +### `ConnectorMatch` + +Rules to match one of the connectors used by the compositor. + +Values of this type should have one of the following forms: + +#### An array + +This rule matches if any of the rules in the array match. + +Each element of this array should be a [ConnectorMatch](#types-ConnectorMatch). + +#### A table + +Describes a rule that matches a subset of connectors. + +This rule matches if all of the specified fields match. + +- Example: + + ```toml + [[connectors]] + match.name = "DP-1" + ``` + +The table has the following fields: + +- `name` (optional): + + The name of the connector. + + These values are not necessarily stable. You can find out the value by running + `jay randr`. + + The value of this field should be a string. + + + +### `DrmDevice` + +Describes configuration to apply to a DRM device (graphics card). + +- Example: To disable direct scanout on a device: + + ```toml + [[drm-devices]] + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + direct-scanout = false + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `name` (optional): + + Assigns a name to the rule in the `match` field. + + This only has an effect when used in the top-level `drm-devices` array. + + The value of this field should be a string. + +- `match` (required): + + The rule by which the DRM devices to modify are selected. + + The value of this field should be a [DrmDeviceMatch](#types-DrmDeviceMatch). + +- `direct-scanout` (optional): + + If specified, enables or disables direct scanout on this device. + + The value of this field should be a boolean. + +- `gfx-api` (optional): + + If specified, sets the graphics API to use for this device. + + The value of this field should be a [GfxApi](#types-GfxApi). + + + +### `DrmDeviceMatch` + +Rules to match one of the DRM devices (graphics cards) used by the compositor. + +Values of this type should have one of the following forms: + +#### An array + +This rule matches if any of the rules in the array match. + +Each element of this array should be a [DrmDeviceMatch](#types-DrmDeviceMatch). + +#### A table + +Describes a rule that matches a subset of DRM devices. + +This rule matches if all of the specified fields match. + +- Example: + + ```toml + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + ``` + +The table has the following fields: + +- `name` (optional): + + The name of another DrmDeviceMatch rule. + + For this rule to match, the referenced rule must match. The name of the rule + should have been defined in the top-level `drm-devices` array. + + This can be used to easily refer to DRM devices. + + - Example: + + ```toml + [shortcuts] + alt-v = { type = "configure-drm-device", dev = { match.name = "dedicated", gfx-api = "Vulkan" } } + alt-o = { type = "configure-drm-device", dev = { match.name = "dedicated", gfx-api = "OpenGl" } } + + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + ``` + + The value of this field should be a string. + +- `syspath` (optional): + + The syspath of the device. + + This is useful if you have multiple copies of the same device installed so that + the PCI numbers are not unique. + + The values are usually stable unless you re-configure your hardware. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" + ``` + + The value of this field should be a string. + +- `devnode` (optional): + + The devnode of the device. + + The values are usually not-stable across PC restarts. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.devnode = "/dev/dri/card0" + ``` + + The value of this field should be a string. + +- `vendor` (optional): + + The name of the vendor. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.vendor = "Advanced Micro Devices, Inc. [AMD/ATI]" + ``` + + The value of this field should be a string. + +- `model` (optional): + + The name of the model. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.vendor = "Raphael" + ``` + + The value of this field should be a string. + +- `pci-vendor` (optional): + + The PCI number of the vendor. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.pci-vendor = 0x1002 + ``` + + The value of this field should be a number. + + The numbers should be integers. + +- `pci-model` (optional): + + The PCI number of the model. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.pci-model = 0x164e + ``` + + The value of this field should be a number. + + The numbers should be integers. + + + +### `Exec` + +Describes how to execute a program. + +- Example 1: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = "alacritty" } + ``` + +- Example 2: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = ["notify-send", "hello world"] } + ``` + +- Example 3: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } + ``` + +Values of this type should have one of the following forms: + +#### A string + +The name of the executable to execute. + +- Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = "alacritty" } + ``` + +#### An array + +The name and arguments of the executable to execute. + +- Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = ["notify-send", "hello world"] } + ``` + +Each element of this array should be a string. + +#### A table + +The name, arguments, and environment variables of the executable to execute. + +- Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } + ``` + +The table has the following fields: + +- `prog` (required): + + The name of the executable. + + The value of this field should be a string. + +- `args` (optional): + + The arguments to pass to the executable. + + The value of this field should be an array of strings. + +- `env` (optional): + + The environment variables to pass to the executable. + + The value of this field should be a table whose values are strings. + + + +### `GfxApi` + +A graphics API used for rendering. + +Values of this type should be strings. + +The string should have one of the following values: + +- `OpenGl`: + + The OpenGL API. + +- `Vulkan`: + + The Vulkan API. + + Note that this API has the following restriction: If any of the DRM devices in + the system use Vulkan, then all devices must support DRM format modifiers. This + is usually the case but not for AMD devices older than RX 5xxx. + + + + +### `Input` + +Describes configuration to apply to an input device. + +- Example: To make mice left handed: + + ```toml + [[inputs]] + match.is-pointer = true + left-handed = true + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `tag` (optional): + + Assigns a name to the rule in the `match` field. + + This only has an effect when used in the top-level `inputs` array. + + The value of this field should be a string. + +- `match` (required): + + The rule by which the input devices to modify are selected. + + The value of this field should be a [InputMatch](#types-InputMatch). + +- `accel-profile` (optional): + + The acceleration profile to use. + + See the libinput documentation for more details. + + The value of this field should be a [AccelProfile](#types-AccelProfile). + +- `accel-speed` (optional): + + The acceleration speed to use. + + Values should be in the range -1 to 1. + + See the libinput documentation for more details. + + The value of this field should be a number. + +- `tap-enabled` (optional): + + Whether tap is enabled for this device. + + See the libinput documentation for more details. + + The value of this field should be a boolean. + +- `tap-drag-enabled` (optional): + + Whether tap drag is enabled for this device. + + See the libinput documentation for more details. + + The value of this field should be a boolean. + +- `tap-drag-lock-enabled` (optional): + + Whether tap drag lock is enabled for this device. + + See the libinput documentation for more details. + + The value of this field should be a boolean. + +- `left-handed` (optional): + + Whether the device is left handed. + + See the libinput documentation for more details. + + The value of this field should be a boolean. + +- `natural-scrolling` (optional): + + Whether the device uses natural scrolling. + + See the libinput documentation for more details. + + The value of this field should be a boolean. + +- `px-per-wheel-scroll` (optional): + + The number of pixels to scroll for each scroll wheel dedent. + + The value of this field should be a boolean. + +- `transform-matrix` (optional): + + A transformation matrix to apply to each motion event of this device. + The matrix should be 2x2. + + - Example: To slow down the mouse to 35% of normal speed: + + ```toml + [[inputs]] + match.is-pointer = true + transform-matrix = [[0.35, 0], [0, 0.35]] + ``` + + The value of this field should be an array of arrays of numbers. + + + +### `InputMatch` + +Rules to match one of the input devices used by the compositor. + +Values of this type should have one of the following forms: + +#### An array + +This rule matches if any of the rules in the array match. + +Each element of this array should be a [InputMatch](#types-InputMatch). + +#### A table + +Describes a rule that matches a subset of input devices. + +This rule matches if all of the specified fields match. + +- Example: + + ```toml + [[inputs]] + match.is-pointer = true + left-handed = true + ``` + +The table has the following fields: + +- `tag` (optional): + + The tag of another InputMatch rule. + + For this rule to match, the referenced rule must match. The name of the rule + should have been defined in the top-level `inputs` array. + + This can be used to easily refer to input devices. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-input", input = { match.tag = "mouse", left-handed = true } } + alt-r = { type = "configure-input", input = { match.tag = "mouse", left-handed = false } } + + [[inputs]] + tag = "mouse" + match.is-pointer = true + ``` + + The value of this field should be a string. + +- `name` (optional): + + The libinput name of the device. + + You can find out the name of the devices by running `jay input`. + + - Example: + + ```toml + [[inputs]] + match.name = "Logitech G300s Optical Gaming Mouse" + left-handed = true + ``` + + The value of this field should be a string. + +- `syspath` (optional): + + The syspath of the device. + + This is useful if you have multiple copies of the same device installed so that + the name is not unique. + + The values are usually stable unless you re-configure your hardware. + + - Example: + + ```toml + [[inputs]] + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.2/5-1.1.2:1.0" + left-handed = true + ``` + + The value of this field should be a string. + +- `devnode` (optional): + + The devnode of the device. + + The values are usually not-stable across PC restarts. + + - Example: + + ```toml + [[inputs]] + match.devnode = "/dev/input/event4" + left-handed = true + ``` + + The value of this field should be a string. + +- `is-keyboard` (optional): + + Whether the devices has been identified as a keyboard. + + - Example: + + ```toml + [[inputs]] + match.is-keyboard = false + left-handed = true + ``` + + The value of this field should be a boolean. + +- `is-pointer` (optional): + + Whether the devices has been identified as a pointer. + + - Example: + + ```toml + [[inputs]] + match.is-pointer = false + left-handed = true + ``` + + The value of this field should be a boolean. + +- `is-touch` (optional): + + Whether the devices has been identified as a touch device. + + - Example: + + ```toml + [[inputs]] + match.is-touch = true + tap-enabled = true + ``` + + The value of this field should be a boolean. + +- `is-tablet-tool` (optional): + + Whether the devices has been identified as a tablet tool. + + - Example: + + ```toml + [[inputs]] + match.is-tablet-tool = true + tap-enabled = true + ``` + + The value of this field should be a boolean. + +- `is-tablet-pad` (optional): + + Whether the devices has been identified as a tablet pad. + + - Example: + + ```toml + [[inputs]] + match.is-tablet-tool = true + tap-enabled = true + ``` + + The value of this field should be a boolean. + +- `is-gesture` (optional): + + Whether the devices has been identified as a switch. + + - Example: + + ```toml + [[inputs]] + match.is-switch = true + ``` + + The value of this field should be a boolean. + + + +### `Keymap` + +A keymap. + +Values of this type should have one of the following forms: + +#### A string + +Defines a keymap by its XKB representation. + +- Example: + + ```toml + keymap = """ + xkb_keymap { + xkb_keycodes { include "evdev+aliases(qwerty)" }; + xkb_types { include "complete" }; + xkb_compat { include "complete" }; + xkb_symbols { include "pc+us+inet(evdev)" }; + }; + """ + ``` + +#### A table + +Defines or references a keymap. + +- Example: + + ```toml + keymap.name = "my-keymap" + + [[keymaps]] + name = "my-keymap" + path = "./my-keymap.xkb" + ``` + +The table has the following fields: + +- `name` (optional): + + Defines a keymap name or references a defined keymap. + + If the value is set in the top-level `keymaps` array, it defines a named + keymap. + + Otherwise it references a named keymap that should have been defined in the + `keymaps` array. + + The value of this field should be a string. + +- `map` (optional): + + Defines a keymap by its XKB representation. + + For each keymap defined in the top-level `keymaps` array, exactly one of `map` + and `path` has to be defined. + + The value of this field should be a string. + +- `path` (optional): + + Loads a keymap's XKB representation from a file. + + If the path is relative, it will be interpreted relative to the Jay config + directory. + + For each keymap defined in the top-level `keymaps` array, exactly one of `map` + and `path` has to be defined. + + The value of this field should be a string. + + + +### `LogLevel` + +A log level. + +Values of this type should be strings. + +The string should have one of the following values: + +- `trace`: + + Trace log level. + +- `debug`: + + Debug log level. + +- `info`: + + Info log level. + +- `warn`: + + Warn log level. + +- `error`: + + Error log level. + + + + +### `MessageFormat` + +A message format used by status programs. + +Values of this type should be strings. + +The string should have one of the following values: + +- `plain`: + + The messages are in plain text. + +- `pango`: + + The messages contain pango markup. + +- `i3bar`: + + The messages are in i3bar format. + + + + +### `Mode` + +The mode of a display. + +- Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + mode = { width = 1920, height = 1080, refresh-rate = 59.94 } + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `width` (required): + + The width of the mode. + + The value of this field should be a number. + + The numbers should be integers. + +- `height` (required): + + The height of the mode. + + The value of this field should be a number. + + The numbers should be integers. + +- `refresh-rate` (optional): + + The refresh rate of the mode in HZ. + + The value of this field should be a number. + + + +### `Output` + +Describes configuration to apply to an output. + +- Example: To set the scale of an output. + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + scale = 1.25 + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `name` (optional): + + Assigns a name to the rule in the `match` field. + + This only has an effect when used in the top-level `outputs` array. + + The value of this field should be a string. + +- `match` (required): + + The rule by which the outputs to modify are selected. + + The value of this field should be a [OutputMatch](#types-OutputMatch). + +- `x` (optional): + + The x coordinate of the output in compositor space. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + +- `y` (optional): + + The y coordinate of the output in compositor space. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + +- `scale` (optional): + + The scale of the output. + + The value of this field should be a number. + + The numbers should be strictly greater than 0. + +- `transform` (optional): + + The transform of the output. + + The value of this field should be a [Transform](#types-Transform). + +- `mode` (optional): + + The mode of the output. + + If the refresh rate is not specified, the first mode with the specified width and + height is used. + + The value of this field should be a [Mode](#types-Mode). + + + +### `OutputMatch` + +Rules to match one of the outputs used by the compositor. + +Values of this type should have one of the following forms: + +#### An array + +This rule matches if any of the rules in the array match. + +Each element of this array should be a [OutputMatch](#types-OutputMatch). + +#### A table + +Describes a rule that matches a subset of outputs. + +This rule matches if all of the specified fields match. + +- Example: + + ```toml + [[outputs]] + name = "right" + match.serial-number = "33K03894SL0" + x = 1920 + y = 0 + ``` + +The table has the following fields: + +- `name` (optional): + + The name of another OutputMatch rule. + + For this rule to match, the referenced rule must match. The name of the rule + should have been defined in the top-level `outputs` array. + + This can be used to easily refer to outputs. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-output", output = { match.name = "right", transform = "none" } } + alt-r = { type = "configure-output", output = { match.name = "right", transform = "rotate-90" } } + + [[outputs]] + name = "right" + match.serial-number = "33K03894SL0" + ``` + + The value of this field should be a string. + +- `connector` (optional): + + The name of the connector the output is connected to. + + You can find out the name of the connector by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.connector = "DP-1" + scale = 1.25 + ``` + + The value of this field should be a string. + +- `serial-number` (optional): + + The serial number of the output. + + You can find out the serial number by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + scale = 1.25 + ``` + + The value of this field should be a string. + +- `manufacturer` (optional): + + The manufacturer of the output. + + You can find out the manufacturer by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.manufacturer = "BNQ" + scale = 1.25 + ``` + + The value of this field should be a string. + +- `model` (optional): + + The model of the output. + + You can find out the model by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.model = "BenQ GW2480" + scale = 1.25 + ``` + + The value of this field should be a string. + + + +### `SimpleActionName` + +The name of a `simple` Action. + +- Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + +Values of this type should be strings. + +The string should have one of the following values: + +- `focus-left`: + + Move the keyboard focus to the left of the currently focused window. + +- `focus-down`: + + Move the keyboard focus down from the currently focused window. + +- `focus-up`: + + Move the keyboard focus up from the currently focused window. + +- `focus-right`: + + Move the keyboard focus to the right of the currently focused window. + +- `move-left`: + + Move the currently focused window one to the left. + +- `move-down`: + + Move the currently focused window one down. + +- `move-up`: + + Move the currently focused window one up. + +- `move-right`: + + Move the currently focused window one to the right. + +- `move-right`: + + Move the currently focused window one to the right. + +- `split-horizontal`: + + Split the currently focused window horizontally. + +- `split-vertical`: + + Split the currently focused window vertically. + +- `toggle-split`: + + Toggle the split of the currently focused container between vertical and + horizontal. + +- `toggle-mono`: + + Toggle the currently focused container between showing a single and all children. + +- `toggle-fullscreen`: + + Toggle the currently focused window between fullscreen and windowed. + +- `focus-parent`: + + Focus the parent of the currently focused window. + +- `close`: + + Close the currently focused window. + +- `disable-pointer-constraint`: + + Disable the currently active pointer constraint, allowing you to move the pointer + outside the window. + + The constraint will be re-enabled when the pointer re-enters the window. + +- `toggle-floating`: + + Toggle the currently focused window between floating and tiled. + +- `quit`: + + Terminate the compositor. + +- `reload-config-toml`: + + Reload the `config.toml`. + +- `reload-config-to`: + + Reload the `config.so`. + +- `none`: + + Perform no action. + + As a special case, if this is the action of a shortcut, the shortcut will be + unbound. This can be used in modes to unbind a key. + + + + +### `Status` + +The configuration of a status program whose output will be shown in the bar. + +- Example: + + ```toml + [status] + format = "i3bar" + exec = "i3status" + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `format` (optional): + + The format used by the program. + + The value of this field should be a [MessageFormat](#types-MessageFormat). + +- `exec` (required): + + The program that will emit the status messages. + + The value of this field should be a [Exec](#types-Exec). + +- `i3bar-separator` (optional): + + The separator to be used between i3bar components. + + The default is ` | `. + + The value of this field should be a string. + + + +### `Theme` + +The theme of the compositor. + +Values of this type should be tables. + +The table has the following fields: + +- `attention-requested-bg-color` (optional): + + The background color of title that have requested attention. + + The value of this field should be a [Color](#types-Color). + +- `bg-color` (optional): + + The background color of the desktop. + + The value of this field should be a [Color](#types-Color). + +- `bar-bg-color` (optional): + + The background color of the bar. + + The value of this field should be a [Color](#types-Color). + +- `bar-status-text-color` (optional): + + The color of the status text in the bar. + + The value of this field should be a [Color](#types-Color). + +- `border-color` (optional): + + The color of the borders between windows. + + The value of this field should be a [Color](#types-Color). + +- `captured-focused-title-bg-color` (optional): + + The background color of focused titles that are being recorded. + + The value of this field should be a [Color](#types-Color). + +- `captured-unfocused-title-bg-color` (optional): + + The background color of unfocused titles that are being recorded. + + The value of this field should be a [Color](#types-Color). + +- `focused-inactive-title-bg-color` (optional): + + The background color of focused titles that are inactive. + + The value of this field should be a [Color](#types-Color). + +- `focused-inactive-title-text-color` (optional): + + The text color of focused titles that are inactive. + + The value of this field should be a [Color](#types-Color). + +- `focused-title-bg-color` (optional): + + The background color of focused titles. + + The value of this field should be a [Color](#types-Color). + +- `focused-title-text-color` (optional): + + The text color of focused titles. + + The value of this field should be a [Color](#types-Color). + +- `separator-color` (optional): + + The color of the separator between titles and window content. + + The value of this field should be a [Color](#types-Color). + +- `unfocused-title-bg-color` (optional): + + The background color of unfocused titles. + + The value of this field should be a [Color](#types-Color). + +- `unfocused-title-text-color` (optional): + + The text color of unfocused titles. + + The value of this field should be a [Color](#types-Color). + +- `border-width` (optional): + + The width of borders between windows. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + +- `title-height` (optional): + + The height of tabs. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + +- `font` (optional): + + The name of the font to use. + + The value of this field should be a string. + + + +### `Transform` + +An output transformation. + +Values of this type should be strings. + +The string should have one of the following values: + +- `none`: + + No transformation. + +- `rotate-90`: + + The content of the output is rotated 90 degrees counter clockwise. + +- `rotate-180`: + + The content of the output is rotated 180 degrees counter clockwise. + +- `rotate-270`: + + The content of the output is rotated 270 degrees counter clockwise. + +- `flip`: + + The content of the output is flipped around the vertical axis. + +- `flip-rotate-90`: + + The content of the output is flipped around the vertical axis and then rotated + 90 degrees counter clockwise. + +- `flip-rotate-180`: + + The content of the output is flipped around the vertical axis and then rotated + 180 degrees counter clockwise. + +- `flip-rotate-270`: + + The content of the output is flipped around the vertical axis and then rotated + 270 degrees counter clockwise. + + + diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml new file mode 100644 index 00000000..58af22a9 --- /dev/null +++ b/toml-spec/spec/spec.yaml @@ -0,0 +1,1871 @@ +Keymap: + description: | + A keymap. + kind: variable + variants: + - kind: string + description: | + Defines a keymap by its XKB representation. + + - Example: + + ```toml + keymap = """ + xkb_keymap { + xkb_keycodes { include "evdev+aliases(qwerty)" }; + xkb_types { include "complete" }; + xkb_compat { include "complete" }; + xkb_symbols { include "pc+us+inet(evdev)" }; + }; + """ + ``` + - kind: table + description: | + Defines or references a keymap. + + - Example: + + ```toml + keymap.name = "my-keymap" + + [[keymaps]] + name = "my-keymap" + path = "./my-keymap.xkb" + ``` + fields: + name: + kind: string + required: false + description: | + Defines a keymap name or references a defined keymap. + + If the value is set in the top-level `keymaps` array, it defines a named + keymap. + + Otherwise it references a named keymap that should have been defined in the + `keymaps` array. + map: + kind: string + required: false + description: | + Defines a keymap by its XKB representation. + + For each keymap defined in the top-level `keymaps` array, exactly one of `map` + and `path` has to be defined. + path: + kind: string + required: false + description: | + Loads a keymap's XKB representation from a file. + + If the path is relative, it will be interpreted relative to the Jay config + directory. + + For each keymap defined in the top-level `keymaps` array, exactly one of `map` + and `path` has to be defined. + + +Action: + description: | + kind: variable + variants: + - kind: string + ref: SimpleActionName + description: | + The value should be the name of a `simple` action. See the description of that + variant for more details. + + - Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + - kind: array + items: + ref: Action + description: | + A list of actions to execute in sequence. + + - Example: + + ```toml + [shortcuts] + alt-q = [ + { type = "exec", exec = ["notify-send", "exiting"] }, + "quit", + ] + ``` + - kind: table + description: '' + types: + simple: + description: | + A simple action that takes no arguments. These are usually written as plain + strings instead. + + - Example 1: + + ```toml + [shortcuts] + alt-q = { type = "simple", cmd = "quit" } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + fields: + cmd: + description: The simple action to execute. + required: true + ref: SimpleActionName + multi: + description: | + A list of actions to execute in sequence. These are usually written as plain + arrays instead. + + - Example 1: + + ```toml + [shortcuts] + alt-q = { type = "multi", actions = ["quit", "quit"] } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-q = ["quit", "quit"] + ``` + fields: + actions: + description: The actions to execute. + required: true + kind: array + items: + ref: Action + exec: + description: | + Executes a program. + + - Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = "alacritty" } + ctrl-b = { type = "exec", exec = ["notify-send", "hello world"] } + ``` + fields: + exec: + description: The command to execute. + required: true + ref: Exec + switch-to-vt: + description: | + Switches to a virtual terminal. + + - Example: + + ```toml + [shortcuts] + ctrl-alt-F1 = { type = "switch-to-vt", num = 1 } + ``` + fields: + num: + description: | + The VT number to switch to. + required: true + kind: number + integer_only: true + minimum: 1 + show-workspace: + description: | + Switches to a workspace. + + - Example: + + ```toml + [shortcuts] + alt-F1 = { type = "show-workspace", name = "1" } + ``` + fields: + name: + description: The name of the workspace. + required: true + kind: string + move-to-workspace: + description: | + Moves the currently focused window to a workspace. + + - Example: + + ```toml + [shortcuts] + alt-F1 = { type = "move-to-workspace", name = "1" } + ``` + fields: + name: + description: The name of the workspace. + required: true + kind: string + configure-connector: + description: | + Applies a configuration to connectors. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "configure-connector", connector = { match.name = "eDP-1", enabled = false } + alt-k = { type = "configure-connector", connector = { match.name = "eDP-1", enabled = true } + ``` + fields: + connector: + description: The connector configuration. + required: true + ref: Connector + configure-input: + description: | + Applies a configuration to input devices. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-input", input = { match.tag = "mouse", left-handed = true } } + alt-r = { type = "configure-input", input = { match.tag = "mouse", left-handed = false } } + + [[inputs]] + tag = "mouse" + match.is-pointer = true + ``` + fields: + input: + description: The input configuration. + required: true + ref: Input + configure-output: + description: | + Applies a configuration to input devices. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-output", output = { match.name = "right", transform = "none" } } + alt-r = { type = "configure-output", output = { match.name = "right", transform = "rotate-90" } } + + [[outputs]] + name = "right" + match.serial-number = "33K03894SL0" + ``` + fields: + output: + description: The output configuration. + required: true + ref: Output + set-env: + description: | + Sets environment variables for all programs started afterwards. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "set-env", env.GTK_THEME = "Adwaita:dark" } + ``` + fields: + env: + description: The environment variables. + required: true + kind: map + values: + kind: string + unset-env: + description: | + Unsets environment variables for all programs started afterwards. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "unset-env", env = ["Adwaita:dark"] } + ``` + fields: + env: + description: The environment variables. + required: true + kind: array + items: + kind: string + set-keymap: + description: | + Sets the keymap. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-keymap", keymap.name = "laptop" } + alt-k = { type = "set-keymap", keymap.name = "external" } + + [[keymaps]] + name = "laptop" + path = "./laptop-keymap.xkb" + + [[keymaps]] + name = "external" + path = "./external-keymap.xkb" + ``` + fields: + keymap: + description: The keymap. + required: true + ref: Keymap + set-status: + description: | + Sets the status command. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-status", status = { exec = "i3status" } } + ``` + fields: + status: + description: | + The status setting. + + Omitting this causes the status to be reset to empty. + required: false + ref: Status + set-theme: + description: | + Sets the theme. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-theme", theme.bg-color = "#ff0000" } + ``` + fields: + theme: + description: The theme. + required: true + ref: Theme + set-log-level: + description: | + Sets the log level of the compositor.. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-log-level", level = "debug" } + ``` + fields: + theme: + description: The log level. + required: true + ref: LogLevel + set-gfx-api: + description: | + Sets the graphics API used by new DRM devices. + + Setting this after the compositor has started usually has no effect. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-gfx-api", api = "Vulan" } + ``` + fields: + api: + description: The API. + required: true + ref: GfxApi + configure-direct-scanout: + description: | + Configure whether the compositor attempts direct scanout of client surfaces. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "configure-direct-scanout", enabled = false } + ``` + fields: + enabled: + description: Whether direct scanout is enabled. + required: true + kind: boolean + configure-drm-device: + description: | + Applies a configuration to DRM devices. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "configure-drm-device", dev = { match.name = "integrated", gfx-api = "Vulkan" } } + + [[drm-devices]] + name = "integrated" + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" + ``` + fields: + dev: + description: The DRM device configuration. + required: true + ref: DrmDevice + set-render-device: + description: | + Sets the render device used for compositing. + + Changing this after the compositor has started might cause client windows to + become invisible until they are resized. + + - Example: + + ```toml + [shortcuts] + alt-j = { type = "set-render-device", dev.name = "integrated" } + + [[drm-devices]] + name = "integrated" + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" + ``` + fields: + dev: + description: | + The rule to find the device. + + The first matching device is used. + required: true + ref: DrmDeviceMatch + + +Exec: + description: | + Describes how to execute a program. + + - Example 1: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = "alacritty" } + ``` + + - Example 2: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = ["notify-send", "hello world"] } + ``` + + - Example 3: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } + ``` + kind: variable + variants: + - kind: string + description: | + The name of the executable to execute. + + - Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = "alacritty" } + ``` + - kind: array + items: + kind: string + description: | + The name and arguments of the executable to execute. + + - Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = ["notify-send", "hello world"] } + ``` + - kind: table + description: | + The name, arguments, and environment variables of the executable to execute. + + - Example: + + ```toml + [shortcuts] + ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } + ``` + fields: + prog: + kind: string + required: true + description: The name of the executable. + args: + kind: array + required: false + items: + kind: string + description: The arguments to pass to the executable. + env: + kind: map + required: false + values: + kind: string + description: The environment variables to pass to the executable. + + +SimpleActionName: + description: | + The name of a `simple` Action. + + - Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + kind: string + values: + - value: focus-left + description: Move the keyboard focus to the left of the currently focused window. + - value: focus-down + description: Move the keyboard focus down from the currently focused window. + - value: focus-up + description: Move the keyboard focus up from the currently focused window. + - value: focus-right + description: Move the keyboard focus to the right of the currently focused window. + - value: move-left + description: Move the currently focused window one to the left. + - value: move-down + description: Move the currently focused window one down. + - value: move-up + description: Move the currently focused window one up. + - value: move-right + description: Move the currently focused window one to the right. + - value: move-right + description: Move the currently focused window one to the right. + - value: split-horizontal + description: Split the currently focused window horizontally. + - value: split-vertical + description: Split the currently focused window vertically. + - value: toggle-split + description: | + Toggle the split of the currently focused container between vertical and + horizontal. + - value: toggle-mono + description: | + Toggle the currently focused container between showing a single and all children. + - value: toggle-fullscreen + description: Toggle the currently focused window between fullscreen and windowed. + - value: focus-parent + description: Focus the parent of the currently focused window. + - value: close + description: Close the currently focused window. + - value: disable-pointer-constraint + description: | + Disable the currently active pointer constraint, allowing you to move the pointer + outside the window. + + The constraint will be re-enabled when the pointer re-enters the window. + - value: toggle-floating + description: Toggle the currently focused window between floating and tiled. + - value: quit + description: Terminate the compositor. + - value: reload-config-toml + description: Reload the `config.toml`. + - value: reload-config-to + description: Reload the `config.so`. + - value: none + description: | + Perform no action. + + As a special case, if this is the action of a shortcut, the shortcut will be + unbound. This can be used in modes to unbind a key. + + +Color: + kind: string + description: | + A color. + + The format should be one of the following: + + - `#rgb` + - `#rrggbb` + - `#rgba` + - `#rrggbba` + + +ConnectorMatch: + kind: variable + description: | + Rules to match one of the connectors used by the compositor. + variants: + - kind: array + items: + ref: ConnectorMatch + description: | + This rule matches if any of the rules in the array match. + - kind: table + description: | + Describes a rule that matches a subset of connectors. + + This rule matches if all of the specified fields match. + + - Example: + + ```toml + [[connectors]] + match.name = "DP-1" + ``` + fields: + name: + kind: string + required: false + description: | + The name of the connector. + + These values are not necessarily stable. You can find out the value by running + `jay randr`. + + +Connector: + kind: table + description: | + Describes configuration to apply to a connector. + + - Example: To disable the built-in display of a laptop: + + ```toml + [[connectors]] + match.name = "eDP-1" + enabled = false + ``` + fields: + match: + ref: ConnectorMatch + required: true + description: | + The rule by which the connectors to modify are selected. + enabled: + kind: boolean + required: false + description: | + If specified, enables or disables the connector. + + +DrmDeviceMatch: + kind: variable + description: | + Rules to match one of the DRM devices (graphics cards) used by the compositor. + variants: + - kind: array + items: + ref: DrmDeviceMatch + description: | + This rule matches if any of the rules in the array match. + - kind: table + description: | + Describes a rule that matches a subset of DRM devices. + + This rule matches if all of the specified fields match. + + - Example: + + ```toml + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + ``` + fields: + name: + kind: string + required: false + description: | + The name of another DrmDeviceMatch rule. + + For this rule to match, the referenced rule must match. The name of the rule + should have been defined in the top-level `drm-devices` array. + + This can be used to easily refer to DRM devices. + + - Example: + + ```toml + [shortcuts] + alt-v = { type = "configure-drm-device", dev = { match.name = "dedicated", gfx-api = "Vulkan" } } + alt-o = { type = "configure-drm-device", dev = { match.name = "dedicated", gfx-api = "OpenGl" } } + + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + ``` + syspath: + kind: string + required: false + description: | + The syspath of the device. + + This is useful if you have multiple copies of the same device installed so that + the PCI numbers are not unique. + + The values are usually stable unless you re-configure your hardware. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" + ``` + devnode: + kind: string + required: false + description: | + The devnode of the device. + + The values are usually not-stable across PC restarts. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.devnode = "/dev/dri/card0" + ``` + vendor: + kind: string + required: false + description: | + The name of the vendor. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.vendor = "Advanced Micro Devices, Inc. [AMD/ATI]" + ``` + model: + kind: string + required: false + description: | + The name of the model. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.vendor = "Raphael" + ``` + pci-vendor: + kind: number + integer_only: true + required: false + description: | + The PCI number of the vendor. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.pci-vendor = 0x1002 + ``` + pci-model: + kind: number + integer_only: true + required: false + description: | + The PCI number of the model. + + - Example: + + ```toml + [[drm-devices]] + name = "integrated" + match.pci-model = 0x164e + ``` + + +DrmDevice: + kind: table + description: | + Describes configuration to apply to a DRM device (graphics card). + + - Example: To disable direct scanout on a device: + + ```toml + [[drm-devices]] + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + direct-scanout = false + ``` + fields: + name: + kind: string + required: false + description: | + Assigns a name to the rule in the `match` field. + + This only has an effect when used in the top-level `drm-devices` array. + match: + ref: DrmDeviceMatch + required: true + description: | + The rule by which the DRM devices to modify are selected. + direct-scanout: + kind: boolean + required: false + description: | + If specified, enables or disables direct scanout on this device. + gfx-api: + ref: GfxApi + required: false + description: | + If specified, sets the graphics API to use for this device. + + +GfxApi: + kind: string + description: A graphics API used for rendering. + values: + - value: OpenGl + description: The OpenGL API. + - value: Vulkan + description: | + The Vulkan API. + + Note that this API has the following restriction: If any of the DRM devices in + the system use Vulkan, then all devices must support DRM format modifiers. This + is usually the case but not for AMD devices older than RX 5xxx. + + +InputMatch: + kind: variable + description: | + Rules to match one of the input devices used by the compositor. + variants: + - kind: array + items: + ref: InputMatch + description: | + This rule matches if any of the rules in the array match. + - kind: table + description: | + Describes a rule that matches a subset of input devices. + + This rule matches if all of the specified fields match. + + - Example: + + ```toml + [[inputs]] + match.is-pointer = true + left-handed = true + ``` + fields: + tag: + kind: string + required: false + description: | + The tag of another InputMatch rule. + + For this rule to match, the referenced rule must match. The name of the rule + should have been defined in the top-level `inputs` array. + + This can be used to easily refer to input devices. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-input", input = { match.tag = "mouse", left-handed = true } } + alt-r = { type = "configure-input", input = { match.tag = "mouse", left-handed = false } } + + [[inputs]] + tag = "mouse" + match.is-pointer = true + ``` + name: + kind: string + required: false + description: | + The libinput name of the device. + + You can find out the name of the devices by running `jay input`. + + - Example: + + ```toml + [[inputs]] + match.name = "Logitech G300s Optical Gaming Mouse" + left-handed = true + ``` + syspath: + kind: string + required: false + description: | + The syspath of the device. + + This is useful if you have multiple copies of the same device installed so that + the name is not unique. + + The values are usually stable unless you re-configure your hardware. + + - Example: + + ```toml + [[inputs]] + match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.2/5-1.1.2:1.0" + left-handed = true + ``` + devnode: + kind: string + required: false + description: | + The devnode of the device. + + The values are usually not-stable across PC restarts. + + - Example: + + ```toml + [[inputs]] + match.devnode = "/dev/input/event4" + left-handed = true + ``` + is-keyboard: + kind: boolean + required: false + description: | + Whether the devices has been identified as a keyboard. + + - Example: + + ```toml + [[inputs]] + match.is-keyboard = false + left-handed = true + ``` + is-pointer: + kind: boolean + required: false + description: | + Whether the devices has been identified as a pointer. + + - Example: + + ```toml + [[inputs]] + match.is-pointer = false + left-handed = true + ``` + is-touch: + kind: boolean + required: false + description: | + Whether the devices has been identified as a touch device. + + - Example: + + ```toml + [[inputs]] + match.is-touch = true + tap-enabled = true + ``` + is-tablet-tool: + kind: boolean + required: false + description: | + Whether the devices has been identified as a tablet tool. + + - Example: + + ```toml + [[inputs]] + match.is-tablet-tool = true + tap-enabled = true + ``` + is-tablet-pad: + kind: boolean + required: false + description: | + Whether the devices has been identified as a tablet pad. + + - Example: + + ```toml + [[inputs]] + match.is-tablet-tool = true + tap-enabled = true + ``` + is-gesture: + kind: boolean + required: false + description: | + Whether the devices has been identified as a switch. + + - Example: + + ```toml + [[inputs]] + match.is-switch = true + ``` + + +Input: + kind: table + description: | + Describes configuration to apply to an input device. + + - Example: To make mice left handed: + + ```toml + [[inputs]] + match.is-pointer = true + left-handed = true + ``` + fields: + tag: + kind: string + required: false + description: | + Assigns a name to the rule in the `match` field. + + This only has an effect when used in the top-level `inputs` array. + match: + ref: InputMatch + required: true + description: | + The rule by which the input devices to modify are selected. + accel-profile: + ref: AccelProfile + required: false + description: | + The acceleration profile to use. + + See the libinput documentation for more details. + accel-speed: + kind: number + required: false + description: | + The acceleration speed to use. + + Values should be in the range -1 to 1. + + See the libinput documentation for more details. + tap-enabled: + kind: boolean + required: false + description: | + Whether tap is enabled for this device. + + See the libinput documentation for more details. + tap-drag-enabled: + kind: boolean + required: false + description: | + Whether tap drag is enabled for this device. + + See the libinput documentation for more details. + tap-drag-lock-enabled: + kind: boolean + required: false + description: | + Whether tap drag lock is enabled for this device. + + See the libinput documentation for more details. + left-handed: + kind: boolean + required: false + description: | + Whether the device is left handed. + + See the libinput documentation for more details. + natural-scrolling: + kind: boolean + required: false + description: | + Whether the device uses natural scrolling. + + See the libinput documentation for more details. + px-per-wheel-scroll: + kind: boolean + required: false + description: | + The number of pixels to scroll for each scroll wheel dedent. + transform-matrix: + kind: array + items: + kind: array + items: + kind: number + required: false + description: | + A transformation matrix to apply to each motion event of this device. + The matrix should be 2x2. + + - Example: To slow down the mouse to 35% of normal speed: + + ```toml + [[inputs]] + match.is-pointer = true + transform-matrix = [[0.35, 0], [0, 0.35]] + ``` + + +AccelProfile: + kind: string + values: + - value: Flat + description: The flat profile. + - value: Adaptive + description: The adaptive profile. + description: | + The acceleration profile to apply to an input device. + + See the libinput documentation for more details. + + +LogLevel: + kind: string + description: A log level. + values: + - value: trace + description: Trace log level. + - value: debug + description: Debug log level. + - value: info + description: Info log level. + - value: warn + description: Warn log level. + - value: error + description: Error log level. + + +Mode: + kind: table + description: | + The mode of a display. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + mode = { width = 1920, height = 1080, refresh-rate = 59.94 } + ``` + fields: + width: + kind: number + integer_only: true + required: true + description: The width of the mode. + height: + kind: number + integer_only: true + required: true + description: The height of the mode. + refresh-rate: + kind: number + required: false + description: The refresh rate of the mode in HZ. + + +OutputMatch: + kind: variable + description: | + Rules to match one of the outputs used by the compositor. + variants: + - kind: array + items: + ref: OutputMatch + description: | + This rule matches if any of the rules in the array match. + - kind: table + description: | + Describes a rule that matches a subset of outputs. + + This rule matches if all of the specified fields match. + + - Example: + + ```toml + [[outputs]] + name = "right" + match.serial-number = "33K03894SL0" + x = 1920 + y = 0 + ``` + fields: + name: + kind: string + required: false + description: | + The name of another OutputMatch rule. + + For this rule to match, the referenced rule must match. The name of the rule + should have been defined in the top-level `outputs` array. + + This can be used to easily refer to outputs. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-output", output = { match.name = "right", transform = "none" } } + alt-r = { type = "configure-output", output = { match.name = "right", transform = "rotate-90" } } + + [[outputs]] + name = "right" + match.serial-number = "33K03894SL0" + ``` + connector: + kind: string + required: false + description: | + The name of the connector the output is connected to. + + You can find out the name of the connector by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.connector = "DP-1" + scale = 1.25 + ``` + serial-number: + kind: string + required: false + description: | + The serial number of the output. + + You can find out the serial number by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + scale = 1.25 + ``` + manufacturer: + kind: string + required: false + description: | + The manufacturer of the output. + + You can find out the manufacturer by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.manufacturer = "BNQ" + scale = 1.25 + ``` + model: + kind: string + required: false + description: | + The model of the output. + + You can find out the model by running `jay randr`. + + - Example: + + ```toml + [[outputs]] + match.model = "BenQ GW2480" + scale = 1.25 + ``` + + +Output: + kind: table + description: | + Describes configuration to apply to an output. + + - Example: To set the scale of an output. + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + scale = 1.25 + ``` + + fields: + name: + kind: string + required: false + description: | + Assigns a name to the rule in the `match` field. + + This only has an effect when used in the top-level `outputs` array. + match: + ref: OutputMatch + required: true + description: | + The rule by which the outputs to modify are selected. + x: + kind: number + integer_only: true + minimum: 0 + required: false + description: | + The x coordinate of the output in compositor space. + y: + kind: number + integer_only: true + minimum: 0 + required: false + description: | + The y coordinate of the output in compositor space. + scale: + kind: number + minimum: 0 + exclusive_minimum: true + required: false + description: | + The scale of the output. + transform: + ref: Transform + required: false + description: | + The transform of the output. + mode: + ref: Mode + required: false + description: | + The mode of the output. + + If the refresh rate is not specified, the first mode with the specified width and + height is used. + + +Transform: + kind: string + description: An output transformation. + values: + - value: none + description: No transformation. + - value: rotate-90 + description: The content of the output is rotated 90 degrees counter clockwise. + - value: rotate-180 + description: The content of the output is rotated 180 degrees counter clockwise. + - value: rotate-270 + description: The content of the output is rotated 270 degrees counter clockwise. + - value: flip + description: The content of the output is flipped around the vertical axis. + - value: flip-rotate-90 + description: | + The content of the output is flipped around the vertical axis and then rotated + 90 degrees counter clockwise. + - value: flip-rotate-180 + description: | + The content of the output is flipped around the vertical axis and then rotated + 180 degrees counter clockwise. + - value: flip-rotate-270 + description: | + The content of the output is flipped around the vertical axis and then rotated + 270 degrees counter clockwise. + + +MessageFormat: + kind: string + description: A message format used by status programs. + values: + - value: plain + description: The messages are in plain text. + - value: pango + description: The messages contain pango markup. + - value: i3bar + description: The messages are in i3bar format. + + +Status: + kind: table + description: | + The configuration of a status program whose output will be shown in the bar. + + - Example: + + ```toml + [status] + format = "i3bar" + exec = "i3status" + ``` + fields: + format: + ref: MessageFormat + required: false + description: The format used by the program. + exec: + ref: Exec + required: true + description: The program that will emit the status messages. + i3bar-separator: + kind: string + required: false + description: | + The separator to be used between i3bar components. + + The default is ` | `. + + +Theme: + kind: table + description: | + The theme of the compositor. + fields: + attention-requested-bg-color: + ref: Color + required: false + description: The background color of title that have requested attention. + bg-color: + ref: Color + required: false + description: The background color of the desktop. + bar-bg-color: + ref: Color + required: false + description: The background color of the bar. + bar-status-text-color: + ref: Color + required: false + description: The color of the status text in the bar. + border-color: + ref: Color + required: false + description: The color of the borders between windows. + captured-focused-title-bg-color: + ref: Color + required: false + description: The background color of focused titles that are being recorded. + captured-unfocused-title-bg-color: + ref: Color + required: false + description: The background color of unfocused titles that are being recorded. + focused-inactive-title-bg-color: + ref: Color + required: false + description: The background color of focused titles that are inactive. + focused-inactive-title-text-color: + ref: Color + required: false + description: The text color of focused titles that are inactive. + focused-title-bg-color: + ref: Color + required: false + description: The background color of focused titles. + focused-title-text-color: + ref: Color + required: false + description: The text color of focused titles. + separator-color: + ref: Color + required: false + description: The color of the separator between titles and window content. + unfocused-title-bg-color: + ref: Color + required: false + description: The background color of unfocused titles. + unfocused-title-text-color: + ref: Color + required: false + description: The text color of unfocused titles. + border-width: + kind: number + integer_only: true + minimum: 0 + required: false + description: The width of borders between windows. + title-height: + kind: number + integer_only: true + minimum: 0 + required: false + description: The height of tabs. + font: + kind: string + required: false + description: The name of the font to use. + + + +Config: + kind: table + description: | + This is the top-level table. + + - Example: + + ```toml + 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 } + # ... + + alt-F1 = { type = "show-workspace", name = "1" } + alt-F2 = { type = "show-workspace", name = "2" } + # ... + + alt-shift-F1 = { type = "move-to-workspace", name = "1" } + alt-shift-F2 = { type = "move-to-workspace", name = "2" } + # ... + ``` + fields: + keymap: + ref: Keymap + required: false + description: | + The keymap to use. + + - Example: + + ```toml + keymap = """ + xkb_keymap { + xkb_keycodes { include "evdev+aliases(qwerty)" }; + xkb_types { include "complete" }; + xkb_compat { include "complete" }; + xkb_symbols { include "pc+us+inet(evdev)" }; + }; + """ + ``` + shortcuts: + kind: map + values: + ref: Action + required: false + description: | + The compositor shortcuts. + + The keys should be in the following format: + + ``` + (MOD-)*KEYSYM + ``` + + `MOD` should be one of `shift`, `lock`, `ctrl`, `mod1`, `mod2`, `mod3`, `mod4`, + `mod5`, `caps`, `alt`, `num`, or `logo`. + + `KEYSYM` should be the name of a keysym. The authorative location for these names + is [1] with the `XKB_KEY_` prefix removed. + + The keysym should be the unmodified keysym. E.g. `shift-q` not `shift-Q`. + + [1]: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h + + - Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + on-graphics-initialized: + ref: Action + required: false + description: | + An action to execute when the graphics have been initialized for the first time. + + This is a good place to start graphical applications. + + - Example: + + ```toml + on-graphics-initialized = { type = "exec", exec = "mako" } + ``` + status: + ref: Status + required: false + description: | + The status program that will be used for the status text. + + - Example: + + ```toml + [status] + format = "i3bar" + exec = "i3status" + ``` + outputs: + kind: array + items: + ref: Output + required: false + description: | + An array of output configurations. + + This can be used to configure outputs and create named outputs that can be + referred to in actions. + + The configurations defined here will only be applied the first time matching + outputs are connected to the compositor after the compositor has started. + If you want change the configuration afterwards, use `jay randr` or a + `configure-output` action. + + - Example: + + ```toml + [[outputs]] + name = "left" + match.serial-number = "33K03894SL0" + x = 0 + y = 0 + + [[outputs]] + name = "right" + match.serial-number = "ETW1M02062SL0" + x = 1920 + y = 0 + ``` + connectors: + kind: array + items: + ref: Connector + required: false + description: | + An array of connector configurations. + + This can be used to configure connectors. + + The configurations defined here will only be applied when the connector is first + discovered by the compositor. This usually never happens after the compositor has + started unless you attach an external graphics card. + + - Example: + + ```toml + [[connectors]] + name = "eDP-1" + enabled = false + ``` + workspace-capture: + kind: boolean + required: false + description: | + Configures whether newly created workspaces can be captured. + + The default is `true`. + env: + kind: map + values: + kind: string + required: false + description: | + Defines environment variables that will be set for all applications. + + - Example: + + ```toml + [env] + GTK_THEME = "Adwaita:dark" + ``` + on-startup: + ref: Action + required: false + description: | + An action to execute as early as possible when the compositor starts. + + At this point, graphics have not yet been initialized. You should not use this + to start graphical applications. See `on-graphics-initialized`. + + This setting has no effect during configuration reloads. + keymaps: + kind: array + items: + ref: Keymap + required: false + description: | + Defines named keymaps. + + These keymaps can be used to easily switch between keymaps for different + keyboards. + + - Example: + + ```toml + keymap.name = "laptop" + + [shortcuts] + alt-j = { type = "set-keymap", keymap.name = "laptop" } + alt-k = { type = "set-keymap", keymap.name = "external" } + + [[keymaps]] + name = "laptop" + path = "./laptop-keymap.xkb" + + [[keymaps]] + name = "external" + path = "./external-keymap.xkb" + ``` + log-level: + ref: LogLevel + required: false + description: | + Sets the log level of the compositor. + + This setting cannot be changed by re-loading the configuration. Use + `jay set-log-level` instead. + + - Example: + + ```toml + log-level = "debug" + ``` + theme: + ref: Theme + required: false + description: | + Sets the theme of the compositor. + gfx-api: + ref: GfxApi + required: false + description: | + Sets the graphics API used for newly discovered DRM devices. + + Changing this setting after the compositor has started usually has no effect + unless you attach an external graphics card. Use `jay randr` to change the API + used by individual devices at runtime. + + - Example: + + ```toml + gfx-api = "Vulkan" + ``` + drm-devices: + kind: array + items: + ref: DrmDevice + required: false + description: | + Names and configures DRM devices. + + These settings are only applied to devices discovered after the configuration + has been loaded. Therefore changing these settings usually has no effect at + runtime unless you attach an external graphics card. You can use `jay randr` or + a `configure-drm-device` Action to change these settings at runtime. + + - Example: + + ```toml + render-device.name = "dedicated" + + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + + [[drm-devices]] + name = "integrated" + match = { pci-vendor = 0x1002, pci-model = 0x164e } + gfx-api = "OpenGl" + ``` + direct-scanout: + kind: boolean + required: false + description: | + Configured whether the compositor attempts direct scanout. + render-device: + ref: DrmDeviceMatch + required: false + description: | + Selects the device to use for rendering in a system with multiple GPUs. + + The first device that matches will be used. + + - Example: + + ```toml + render-device.name = "dedicated" + + [[drm-devices]] + name = "dedicated" + match = { pci-vendor = 0x1002, pci-model = 0x73ff } + ``` + inputs: + kind: array + items: + ref: Input + required: false + description: | + Names and configures input devices. + + These settings are only applied to devices connected after the configuration + has been loaded. You can apply setting without re-connecting the device by using + `jay input` or a `configure-input` Action. + + - Example: + + ```toml + render-device.name = "dedicated" + + [[inputs]] + match.is-pointer = true + left-handed = true + transform-matrix = [[0.35, 0], [0, 0.35]] + tap-enabled = true + ``` + on-idle: + ref: Action + required: false + description: | + An action to execute when the compositor becomes idle. + + - Example: + + ```toml + on-idle = { type = "exec", exec = "lock" } + ``` diff --git a/toml-spec/spec/template.md b/toml-spec/spec/template.md new file mode 100644 index 00000000..44563916 --- /dev/null +++ b/toml-spec/spec/template.md @@ -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 + diff --git a/toml-spec/src/json_schema.rs b/toml-spec/src/json_schema.rs new file mode 100644 index 00000000..68f4a61d --- /dev/null +++ b/toml-spec/src/json_schema.rs @@ -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)], +) -> 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) -> 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) -> 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}) +} diff --git a/toml-spec/src/main.rs b/toml-spec/src/main.rs new file mode 100644 index 00000000..18f3993b --- /dev/null +++ b/toml-spec/src/main.rs @@ -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>> { + 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(()) +} diff --git a/toml-spec/src/markdown.rs b/toml-spec/src/markdown.rs new file mode 100644 index 00000000..75e6055b --- /dev/null +++ b/toml-spec/src/markdown.rs @@ -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)]) -> 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, + name: &str, + spec: &Described, +) -> Result<()> { + writeln!(buf, "")?; + 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, 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, 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, + spec: &'a RefOrSpec, + plural: bool, +) -> Result> { + 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, + 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, 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, 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, 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(()) +} diff --git a/toml-spec/src/types.rs b/toml-spec/src/types.rs new file mode 100644 index 00000000..b5dabf41 --- /dev/null +++ b/toml-spec/src/types.rs @@ -0,0 +1,184 @@ +use { + error_reporter::Report, + indexmap::IndexMap, + serde::{ + de::{DeserializeOwned, Error}, + Deserialize, Deserializer, + }, +}; + +#[derive(Debug, Deserialize)] +pub struct Described { + pub description: String, + #[serde(flatten)] + pub value: T, +} + +#[derive(Debug)] +pub enum TopLevelTypeSpec { + Variable { + variants: Vec>, + }, + Single(VariantSpec), +} + +#[derive(Debug)] +pub enum TableSpec { + Tagged { + types: IndexMap>, + }, + Single(SingleTableSpec), +} + +#[derive(Debug, Deserialize)] +pub struct SingleTableSpec { + pub fields: IndexMap>, +} + +#[derive(Debug, Deserialize)] +pub struct TableFieldSpec { + pub required: bool, + #[serde(flatten)] + pub kind: RefOrSpec, +} + +#[derive(Debug)] +pub enum RefOrSpec { + Ref { name: String }, + Spec(T), +} + +#[derive(Debug, Deserialize)] +pub struct StringSpec { + pub values: Option>>, +} + +#[derive(Debug, Deserialize)] +pub struct StringSpecValue { + pub value: String, +} + +#[derive(Debug, Deserialize)] +pub struct NumberSpec { + #[serde(default)] + pub integer_only: bool, + pub minimum: Option, + #[serde(default)] + pub exclusive_minimum: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case", tag = "kind")] +pub enum VariantSpec { + String(RefOrSpec), + Number(RefOrSpec), + Boolean, + Array(RefOrSpec), + Table(RefOrSpec), +} + +#[derive(Debug, Deserialize)] +pub struct ArraySpec { + pub items: Box>, +} + +#[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>, +} + +impl<'de> Deserialize<'de> for TopLevelTypeSpec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let v = serde_yaml::Value::deserialize(deserializer)?; + #[derive(Debug, Deserialize)] + struct Variable { + variants: Vec>, + } + 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(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let v = serde_yaml::Value::deserialize(deserializer)?; + #[derive(Debug, Deserialize)] + struct Tagged { + types: IndexMap>, + } + 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 { + fn deserialize(deserializer: D) -> Result + 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) + } +}