diff --git a/.builds/unit-tests.yml b/.builds/unit-tests.yml index 140ecddb..ec92b6af 100644 --- a/.builds/unit-tests.yml +++ b/.builds/unit-tests.yml @@ -6,6 +6,8 @@ tasks: sudo pacman -Syu --noconfirm sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake rustup toolchain install stable + cd jay + git submodule update --init - test: | cd jay cargo test 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..3c72f148 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", @@ -292,13 +292,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] -name = "default-config" -version = "0.1.0" +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "chrono", - "jay-config", - "log", - "rand", + "powerfmt", ] [[package]] @@ -479,12 +478,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -516,7 +516,6 @@ dependencies = [ "chrono", "clap", "clap_complete", - "default-config", "dirs", "futures-util", "gpu-alloc", @@ -525,6 +524,7 @@ dependencies = [ "indexmap", "isnt", "jay-config", + "jay-toml-config", "libloading 0.8.1", "log", "num-derive", @@ -557,6 +557,23 @@ dependencies = [ "uapi", ] +[[package]] +name = "jay-toml-config" +version = "0.1.0" +dependencies = [ + "ahash", + "bstr", + "error_reporter", + "indexmap", + "jay-config", + "log", + "phf", + "serde_json", + "simplelog", + "thiserror", + "walkdir", +] + [[package]] name = "js-sys" version = "0.3.67" @@ -627,9 +644,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" @@ -646,6 +663,12 @@ dependencies = [ "adler", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.1" @@ -666,6 +689,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.32.2" @@ -710,6 +742,48 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -742,6 +816,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -894,6 +974,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -902,18 +991,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", @@ -926,11 +1015,25 @@ version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ + "indexmap", "itoa", "ryu", "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "shaderc" version = "0.8.3" @@ -952,6 +1055,23 @@ dependencies = [ "roxmltree", ] +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -995,6 +1115,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.3.0" @@ -1025,6 +1154,39 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1040,6 +1202,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml-spec" +version = "0.1.0" +dependencies = [ + "anyhow", + "error_reporter", + "indexmap", + "serde", + "serde_json", + "serde_yaml", +] + [[package]] name = "uapi" version = "0.2.13" @@ -1072,6 +1246,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "utf8parse" version = "0.2.1" @@ -1084,6 +1264,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1160,6 +1350,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 763b423b..7bac7c86 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" } +jay-toml-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/jay-config/src/_private.rs b/jay-config/src/_private.rs index 04a26cf2..cf4b6064 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -62,3 +62,5 @@ impl WireMode { #[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct PollableId(pub u64); + +pub const DEFAULT_SEAT_NAME: &str = "default"; diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 305690e9..22fd8561 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -506,6 +506,14 @@ impl Client { self.send(&ClientMessage::SetEnv { key, val }); } + pub fn set_log_level(&self, level: LogLevel) { + self.send(&ClientMessage::SetLogLevel { level }) + } + + pub fn unset_env(&self, key: &str) { + self.send(&ClientMessage::UnsetEnv { key }); + } + pub fn set_status(&self, status: &str) { self.send(&ClientMessage::SetStatus { status }); } @@ -576,6 +584,16 @@ impl Client { self.send(&ClientMessage::SetDoubleClickDistance { dist }); } + pub fn disable_default_seat(&self) { + self.send(&ClientMessage::DisableDefaultSeat); + } + + pub fn connector_get_position(&self, connector: Connector) -> (i32, i32) { + let res = self.send_with_response(&ClientMessage::ConnectorGetPosition { connector }); + get_response!(res, (0, 0), ConnectorGetPosition { x, y }); + (x, y) + } + pub fn connector_set_position(&self, connector: Connector, x: i32, y: i32) { self.send(&ClientMessage::ConnectorSetPosition { connector, x, y }); } @@ -591,9 +609,49 @@ impl Client { }); } - pub fn device_connectors(&self, device: DrmDevice) -> Vec { - let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device }); - get_response!(res, vec![], GetDeviceConnectors { connectors }); + pub fn connector_get_name(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorName { connector }); + get_response!(res, String::new(), GetConnectorName { name }); + name + } + + pub fn connector_get_model(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorModel { connector }); + get_response!(res, String::new(), GetConnectorModel { model }); + model + } + + pub fn connector_get_manufacturer(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorManufacturer { connector }); + get_response!( + res, + String::new(), + GetConnectorManufacturer { manufacturer } + ); + manufacturer + } + + pub fn connector_get_serial_number(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorSerialNumber { connector }); + get_response!( + res, + String::new(), + GetConnectorSerialNumber { serial_number } + ); + serial_number + } + + pub fn connectors(&self, device: Option) -> Vec { + if let Some(device) = device { + let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device }); + get_response!(res, vec![], GetConnectors { connectors }); + return connectors; + } + let res = self.send_with_response(&ClientMessage::GetConnectors { + device, + connected_only: false, + }); + get_response!(res, vec![], GetConnectors { connectors }); connectors } @@ -603,6 +661,12 @@ impl Client { syspath } + pub fn drm_device_devnode(&self, device: DrmDevice) -> String { + let res = self.send_with_response(&ClientMessage::GetDrmDeviceDevnode { device }); + get_response!(res, String::new(), GetDrmDeviceDevnode { devnode }); + devnode + } + pub fn drm_device_vendor(&self, device: DrmDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDrmDeviceVendor { device }); get_response!(res, String::new(), GetDrmDeviceVendor { vendor }); @@ -723,6 +787,22 @@ impl Client { self.on_devices_enumerated.set(Some(Box::new(f))); } + pub fn config_dir(&self) -> String { + let res = self.send_with_response(&ClientMessage::GetConfigDir); + get_response!(res, String::new(), GetConfigDir { dir }); + dir + } + + pub fn workspaces(&self) -> Vec { + let res = self.send_with_response(&ClientMessage::GetWorkspaces); + get_response!(res, vec![], GetWorkspaces { workspaces }); + workspaces + } + + pub fn set_idle(&self, timeout: Duration) { + self.send(&ClientMessage::SetIdle { timeout }) + } + pub fn set_seat(&self, device: InputDevice, seat: Seat) { self.send(&ClientMessage::SetSeat { device, seat }) } @@ -772,12 +852,28 @@ impl Client { name } + pub fn input_device_syspath(&self, device: InputDevice) -> String { + let res = self.send_with_response(&ClientMessage::GetInputDeviceSyspath { device }); + get_response!(res, String::new(), GetInputDeviceSyspath { syspath }); + syspath + } + + pub fn input_device_devnode(&self, device: InputDevice) -> String { + let res = self.send_with_response(&ClientMessage::GetInputDeviceDevnode { device }); + get_response!(res, String::new(), GetInputDeviceDevnode { devnode }); + devnode + } + pub fn has_capability(&self, device: InputDevice, cap: Capability) -> bool { let res = self.send_with_response(&ClientMessage::HasCapability { device, cap }); get_response!(res, false, HasCapability { has }); has } + pub fn destroy_keymap(&self, keymap: Keymap) { + self.send(&ClientMessage::DestroyKeymap { keymap }) + } + pub fn seat_set_keymap(&self, seat: Seat, keymap: Keymap) { self.send(&ClientMessage::SeatSetKeymap { seat, keymap }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index f23896cd..377bd3bc 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -381,6 +381,49 @@ pub enum ClientMessage<'a> { env: Vec<(String, String)>, fds: Vec<(i32, i32)>, }, + DisableDefaultSeat, + DestroyKeymap { + keymap: Keymap, + }, + GetConnectorName { + connector: Connector, + }, + GetConnectorModel { + connector: Connector, + }, + GetConnectorManufacturer { + connector: Connector, + }, + GetConnectorSerialNumber { + connector: Connector, + }, + GetConnectors { + device: Option, + connected_only: bool, + }, + ConnectorGetPosition { + connector: Connector, + }, + GetConfigDir, + GetWorkspaces, + UnsetEnv { + key: &'a str, + }, + SetLogLevel { + level: LogLevel, + }, + GetDrmDeviceDevnode { + device: DrmDevice, + }, + GetInputDeviceSyspath { + device: InputDevice, + }, + GetInputDeviceDevnode { + device: InputDevice, + }, + SetIdle { + timeout: Duration, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -440,7 +483,7 @@ pub enum Response { GetFullscreen { fullscreen: bool, }, - GetDeviceConnectors { + GetConnectors { connectors: Vec, }, GetDrmDeviceSyspath { @@ -489,6 +532,37 @@ pub enum Response { AddPollable { id: Result, }, + GetConnectorName { + name: String, + }, + GetConnectorModel { + model: String, + }, + GetConnectorManufacturer { + manufacturer: String, + }, + GetConnectorSerialNumber { + serial_number: String, + }, + ConnectorGetPosition { + x: i32, + y: i32, + }, + GetConfigDir { + dir: String, + }, + GetWorkspaces { + workspaces: Vec, + }, + GetDrmDeviceDevnode { + devnode: String, + }, + GetInputDeviceSyspath { + syspath: String, + }, + GetInputDeviceDevnode { + devnode: String, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/exec.rs b/jay-config/src/exec.rs index f1c2e2ee..4c858900 100644 --- a/jay-config/src/exec.rs +++ b/jay-config/src/exec.rs @@ -9,6 +9,13 @@ pub fn set_env(key: &str, val: &str) { get!().set_env(key, val); } +/// Unsets an environment variable. +/// +/// This does not affect the compositor itself but only programs spawned by the compositor. +pub fn unset_env(key: &str) { + get!().unset_env(key); +} + /// A command to be spawned. pub struct Command { pub(crate) prog: String, diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index a3999ec6..58666aee 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -8,6 +8,7 @@ use { input::{acceleration::AccelProfile, capability::Capability}, keyboard::Keymap, Axis, Direction, ModifiedKeySym, Workspace, + _private::DEFAULT_SEAT_NAME, }, serde::{Deserialize, Serialize}, std::time::Duration, @@ -112,6 +113,20 @@ impl InputDevice { pub fn set_natural_scrolling_enabled(self, enabled: bool) { get!().set_input_natural_scrolling_enabled(self, enabled); } + + /// Returns the syspath of this device. + /// + /// E.g. `/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.3/5-1.1.3:1.0`. + pub fn syspath(self) -> String { + get!(String::new()).input_device_syspath(self) + } + + /// Returns the devnode of this device. + /// + /// E.g. `/dev/input/event7`. + pub fn devnode(self) -> String { + get!(String::new()).input_device_devnode(self) + } } /// A seat. @@ -319,10 +334,20 @@ pub fn input_devices() -> Vec { /// Returns or creates a seat. /// /// Seats are identified by their name. If no seat with the name exists, a new seat will be created. +/// +/// NOTE: You should prefer [`get_default_seat`] instead. Most applications cannot handle more than +/// one seat and will only process input from one of the seats. pub fn get_seat(name: &str) -> Seat { get!(Seat(0)).get_seat(name) } +/// Returns or creates the default seat. +/// +/// This is equivalent to `get_seat("default")`. +pub fn get_default_seat() -> Seat { + get_seat(DEFAULT_SEAT_NAME) +} + /// Sets a closure to run when a new seat has been created. pub fn on_new_seat(f: F) { get!().on_new_seat(f) @@ -357,3 +382,14 @@ pub fn set_double_click_time(duration: Duration) { pub fn set_double_click_distance(distance: i32) { get!().set_double_click_distance(distance) } + +/// Disables the creation of a default seat. +/// +/// Unless this function is called at startup of the compositor, a seat called `default` +/// will automatically be created. +/// +/// When a new input device is attached and a seat called `default` exists, the input +/// device is initially attached to this seat. +pub fn disable_default_seat() { + get!().disable_default_seat(); +} diff --git a/jay-config/src/keyboard/mod.rs b/jay-config/src/keyboard/mod.rs index a061840e..8630926c 100644 --- a/jay-config/src/keyboard/mod.rs +++ b/jay-config/src/keyboard/mod.rs @@ -59,6 +59,15 @@ impl Keymap { pub fn is_invalid(self) -> bool { self == Self::INVALID } + + /// Destroys this reference to the keymap. + /// + /// Seats that are currently using this keymap are unaffected. + pub fn destroy(self) { + if self.is_valid() { + get!().destroy_keymap(self); + } + } } /// Parses a keymap. diff --git a/jay-config/src/keyboard/syms.rs b/jay-config/src/keyboard/syms.rs index 91556754..49c79287 100644 --- a/jay-config/src/keyboard/syms.rs +++ b/jay-config/src/keyboard/syms.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct KeySym(pub u32); +pub const SYM_NoSymbol: KeySym = KeySym(0x000000); +pub const SYM_VoidSymbol: KeySym = KeySym(0xffffff); pub const SYM_BackSpace: KeySym = KeySym(0xff08); pub const SYM_Tab: KeySym = KeySym(0xff09); pub const SYM_Linefeed: KeySym = KeySym(0xff0a); @@ -269,8 +271,11 @@ pub const SYM_dead_O: KeySym = KeySym(0xfe87); pub const SYM_dead_u: KeySym = KeySym(0xfe88); pub const SYM_dead_U: KeySym = KeySym(0xfe89); pub const SYM_dead_small_schwa: KeySym = KeySym(0xfe8a); +pub const SYM_dead_schwa: KeySym = KeySym(0xfe8a); pub const SYM_dead_capital_schwa: KeySym = KeySym(0xfe8b); +pub const SYM_dead_SCHWA: KeySym = KeySym(0xfe8b); pub const SYM_dead_greek: KeySym = KeySym(0xfe8c); +pub const SYM_dead_hamza: KeySym = KeySym(0xfe8d); pub const SYM_First_Virtual_Screen: KeySym = KeySym(0xfed0); pub const SYM_Prev_Virtual_Screen: KeySym = KeySym(0xfed1); pub const SYM_Next_Virtual_Screen: KeySym = KeySym(0xfed2); @@ -462,6 +467,7 @@ pub const SYM_diaeresis: KeySym = KeySym(0x00a8); pub const SYM_copyright: KeySym = KeySym(0x00a9); pub const SYM_ordfeminine: KeySym = KeySym(0x00aa); pub const SYM_guillemotleft: KeySym = KeySym(0x00ab); +pub const SYM_guillemetleft: KeySym = KeySym(0x00ab); pub const SYM_notsign: KeySym = KeySym(0x00ac); pub const SYM_hyphen: KeySym = KeySym(0x00ad); pub const SYM_registered: KeySym = KeySym(0x00ae); @@ -477,7 +483,9 @@ pub const SYM_periodcentered: KeySym = KeySym(0x00b7); pub const SYM_cedilla: KeySym = KeySym(0x00b8); pub const SYM_onesuperior: KeySym = KeySym(0x00b9); pub const SYM_masculine: KeySym = KeySym(0x00ba); +pub const SYM_ordmasculine: KeySym = KeySym(0x00ba); pub const SYM_guillemotright: KeySym = KeySym(0x00bb); +pub const SYM_guillemetright: KeySym = KeySym(0x00bb); pub const SYM_onequarter: KeySym = KeySym(0x00bc); pub const SYM_onehalf: KeySym = KeySym(0x00bd); pub const SYM_threequarters: KeySym = KeySym(0x00be); @@ -1754,12 +1762,12 @@ pub const SYM_partdifferential: KeySym = KeySym(0x1002202); pub const SYM_emptyset: KeySym = KeySym(0x1002205); pub const SYM_elementof: KeySym = KeySym(0x1002208); pub const SYM_notelementof: KeySym = KeySym(0x1002209); -pub const SYM_containsas: KeySym = KeySym(0x100220B); -pub const SYM_squareroot: KeySym = KeySym(0x100221A); -pub const SYM_cuberoot: KeySym = KeySym(0x100221B); -pub const SYM_fourthroot: KeySym = KeySym(0x100221C); -pub const SYM_dintegral: KeySym = KeySym(0x100222C); -pub const SYM_tintegral: KeySym = KeySym(0x100222D); +pub const SYM_containsas: KeySym = KeySym(0x100220b); +pub const SYM_squareroot: KeySym = KeySym(0x100221a); +pub const SYM_cuberoot: KeySym = KeySym(0x100221b); +pub const SYM_fourthroot: KeySym = KeySym(0x100221c); +pub const SYM_dintegral: KeySym = KeySym(0x100222c); +pub const SYM_tintegral: KeySym = KeySym(0x100222d); pub const SYM_because: KeySym = KeySym(0x1002235); pub const SYM_approxeq: KeySym = KeySym(0x1002248); pub const SYM_notapproxeq: KeySym = KeySym(0x1002247); @@ -2111,190 +2119,190 @@ pub const SYM_Sinh_lu2: KeySym = KeySym(0x1000ddf); pub const SYM_Sinh_ruu2: KeySym = KeySym(0x1000df2); pub const SYM_Sinh_luu2: KeySym = KeySym(0x1000df3); pub const SYM_Sinh_kunddaliya: KeySym = KeySym(0x1000df4); -pub const SYM_XF86ModeLock: KeySym = KeySym(0x1008FF01); -pub const SYM_XF86MonBrightnessUp: KeySym = KeySym(0x1008FF02); -pub const SYM_XF86MonBrightnessDown: KeySym = KeySym(0x1008FF03); -pub const SYM_XF86KbdLightOnOff: KeySym = KeySym(0x1008FF04); -pub const SYM_XF86KbdBrightnessUp: KeySym = KeySym(0x1008FF05); -pub const SYM_XF86KbdBrightnessDown: KeySym = KeySym(0x1008FF06); -pub const SYM_XF86MonBrightnessCycle: KeySym = KeySym(0x1008FF07); -pub const SYM_XF86Standby: KeySym = KeySym(0x1008FF10); -pub const SYM_XF86AudioLowerVolume: KeySym = KeySym(0x1008FF11); -pub const SYM_XF86AudioMute: KeySym = KeySym(0x1008FF12); -pub const SYM_XF86AudioRaiseVolume: KeySym = KeySym(0x1008FF13); -pub const SYM_XF86AudioPlay: KeySym = KeySym(0x1008FF14); -pub const SYM_XF86AudioStop: KeySym = KeySym(0x1008FF15); -pub const SYM_XF86AudioPrev: KeySym = KeySym(0x1008FF16); -pub const SYM_XF86AudioNext: KeySym = KeySym(0x1008FF17); -pub const SYM_XF86HomePage: KeySym = KeySym(0x1008FF18); -pub const SYM_XF86Mail: KeySym = KeySym(0x1008FF19); -pub const SYM_XF86Start: KeySym = KeySym(0x1008FF1A); -pub const SYM_XF86Search: KeySym = KeySym(0x1008FF1B); -pub const SYM_XF86AudioRecord: KeySym = KeySym(0x1008FF1C); -pub const SYM_XF86Calculator: KeySym = KeySym(0x1008FF1D); -pub const SYM_XF86Memo: KeySym = KeySym(0x1008FF1E); -pub const SYM_XF86ToDoList: KeySym = KeySym(0x1008FF1F); -pub const SYM_XF86Calendar: KeySym = KeySym(0x1008FF20); -pub const SYM_XF86PowerDown: KeySym = KeySym(0x1008FF21); -pub const SYM_XF86ContrastAdjust: KeySym = KeySym(0x1008FF22); -pub const SYM_XF86RockerUp: KeySym = KeySym(0x1008FF23); -pub const SYM_XF86RockerDown: KeySym = KeySym(0x1008FF24); -pub const SYM_XF86RockerEnter: KeySym = KeySym(0x1008FF25); -pub const SYM_XF86Back: KeySym = KeySym(0x1008FF26); -pub const SYM_XF86Forward: KeySym = KeySym(0x1008FF27); -pub const SYM_XF86Stop: KeySym = KeySym(0x1008FF28); -pub const SYM_XF86Refresh: KeySym = KeySym(0x1008FF29); -pub const SYM_XF86PowerOff: KeySym = KeySym(0x1008FF2A); -pub const SYM_XF86WakeUp: KeySym = KeySym(0x1008FF2B); -pub const SYM_XF86Eject: KeySym = KeySym(0x1008FF2C); -pub const SYM_XF86ScreenSaver: KeySym = KeySym(0x1008FF2D); -pub const SYM_XF86WWW: KeySym = KeySym(0x1008FF2E); -pub const SYM_XF86Sleep: KeySym = KeySym(0x1008FF2F); -pub const SYM_XF86Favorites: KeySym = KeySym(0x1008FF30); -pub const SYM_XF86AudioPause: KeySym = KeySym(0x1008FF31); -pub const SYM_XF86AudioMedia: KeySym = KeySym(0x1008FF32); -pub const SYM_XF86MyComputer: KeySym = KeySym(0x1008FF33); -pub const SYM_XF86VendorHome: KeySym = KeySym(0x1008FF34); -pub const SYM_XF86LightBulb: KeySym = KeySym(0x1008FF35); -pub const SYM_XF86Shop: KeySym = KeySym(0x1008FF36); -pub const SYM_XF86History: KeySym = KeySym(0x1008FF37); -pub const SYM_XF86OpenURL: KeySym = KeySym(0x1008FF38); -pub const SYM_XF86AddFavorite: KeySym = KeySym(0x1008FF39); -pub const SYM_XF86HotLinks: KeySym = KeySym(0x1008FF3A); -pub const SYM_XF86BrightnessAdjust: KeySym = KeySym(0x1008FF3B); -pub const SYM_XF86Finance: KeySym = KeySym(0x1008FF3C); -pub const SYM_XF86Community: KeySym = KeySym(0x1008FF3D); -pub const SYM_XF86AudioRewind: KeySym = KeySym(0x1008FF3E); -pub const SYM_XF86BackForward: KeySym = KeySym(0x1008FF3F); -pub const SYM_XF86Launch0: KeySym = KeySym(0x1008FF40); -pub const SYM_XF86Launch1: KeySym = KeySym(0x1008FF41); -pub const SYM_XF86Launch2: KeySym = KeySym(0x1008FF42); -pub const SYM_XF86Launch3: KeySym = KeySym(0x1008FF43); -pub const SYM_XF86Launch4: KeySym = KeySym(0x1008FF44); -pub const SYM_XF86Launch5: KeySym = KeySym(0x1008FF45); -pub const SYM_XF86Launch6: KeySym = KeySym(0x1008FF46); -pub const SYM_XF86Launch7: KeySym = KeySym(0x1008FF47); -pub const SYM_XF86Launch8: KeySym = KeySym(0x1008FF48); -pub const SYM_XF86Launch9: KeySym = KeySym(0x1008FF49); -pub const SYM_XF86LaunchA: KeySym = KeySym(0x1008FF4A); -pub const SYM_XF86LaunchB: KeySym = KeySym(0x1008FF4B); -pub const SYM_XF86LaunchC: KeySym = KeySym(0x1008FF4C); -pub const SYM_XF86LaunchD: KeySym = KeySym(0x1008FF4D); -pub const SYM_XF86LaunchE: KeySym = KeySym(0x1008FF4E); -pub const SYM_XF86LaunchF: KeySym = KeySym(0x1008FF4F); -pub const SYM_XF86ApplicationLeft: KeySym = KeySym(0x1008FF50); -pub const SYM_XF86ApplicationRight: KeySym = KeySym(0x1008FF51); -pub const SYM_XF86Book: KeySym = KeySym(0x1008FF52); -pub const SYM_XF86CD: KeySym = KeySym(0x1008FF53); -pub const SYM_XF86Calculater: KeySym = KeySym(0x1008FF54); -pub const SYM_XF86Clear: KeySym = KeySym(0x1008FF55); -pub const SYM_XF86Close: KeySym = KeySym(0x1008FF56); -pub const SYM_XF86Copy: KeySym = KeySym(0x1008FF57); -pub const SYM_XF86Cut: KeySym = KeySym(0x1008FF58); -pub const SYM_XF86Display: KeySym = KeySym(0x1008FF59); -pub const SYM_XF86DOS: KeySym = KeySym(0x1008FF5A); -pub const SYM_XF86Documents: KeySym = KeySym(0x1008FF5B); -pub const SYM_XF86Excel: KeySym = KeySym(0x1008FF5C); -pub const SYM_XF86Explorer: KeySym = KeySym(0x1008FF5D); -pub const SYM_XF86Game: KeySym = KeySym(0x1008FF5E); -pub const SYM_XF86Go: KeySym = KeySym(0x1008FF5F); -pub const SYM_XF86iTouch: KeySym = KeySym(0x1008FF60); -pub const SYM_XF86LogOff: KeySym = KeySym(0x1008FF61); -pub const SYM_XF86Market: KeySym = KeySym(0x1008FF62); -pub const SYM_XF86Meeting: KeySym = KeySym(0x1008FF63); -pub const SYM_XF86MenuKB: KeySym = KeySym(0x1008FF65); -pub const SYM_XF86MenuPB: KeySym = KeySym(0x1008FF66); -pub const SYM_XF86MySites: KeySym = KeySym(0x1008FF67); -pub const SYM_XF86New: KeySym = KeySym(0x1008FF68); -pub const SYM_XF86News: KeySym = KeySym(0x1008FF69); -pub const SYM_XF86OfficeHome: KeySym = KeySym(0x1008FF6A); -pub const SYM_XF86Open: KeySym = KeySym(0x1008FF6B); -pub const SYM_XF86Option: KeySym = KeySym(0x1008FF6C); -pub const SYM_XF86Paste: KeySym = KeySym(0x1008FF6D); -pub const SYM_XF86Phone: KeySym = KeySym(0x1008FF6E); -pub const SYM_XF86Q: KeySym = KeySym(0x1008FF70); -pub const SYM_XF86Reply: KeySym = KeySym(0x1008FF72); -pub const SYM_XF86Reload: KeySym = KeySym(0x1008FF73); -pub const SYM_XF86RotateWindows: KeySym = KeySym(0x1008FF74); -pub const SYM_XF86RotationPB: KeySym = KeySym(0x1008FF75); -pub const SYM_XF86RotationKB: KeySym = KeySym(0x1008FF76); -pub const SYM_XF86Save: KeySym = KeySym(0x1008FF77); -pub const SYM_XF86ScrollUp: KeySym = KeySym(0x1008FF78); -pub const SYM_XF86ScrollDown: KeySym = KeySym(0x1008FF79); -pub const SYM_XF86ScrollClick: KeySym = KeySym(0x1008FF7A); -pub const SYM_XF86Send: KeySym = KeySym(0x1008FF7B); -pub const SYM_XF86Spell: KeySym = KeySym(0x1008FF7C); -pub const SYM_XF86SplitScreen: KeySym = KeySym(0x1008FF7D); -pub const SYM_XF86Support: KeySym = KeySym(0x1008FF7E); -pub const SYM_XF86TaskPane: KeySym = KeySym(0x1008FF7F); -pub const SYM_XF86Terminal: KeySym = KeySym(0x1008FF80); -pub const SYM_XF86Tools: KeySym = KeySym(0x1008FF81); -pub const SYM_XF86Travel: KeySym = KeySym(0x1008FF82); -pub const SYM_XF86UserPB: KeySym = KeySym(0x1008FF84); -pub const SYM_XF86User1KB: KeySym = KeySym(0x1008FF85); -pub const SYM_XF86User2KB: KeySym = KeySym(0x1008FF86); -pub const SYM_XF86Video: KeySym = KeySym(0x1008FF87); -pub const SYM_XF86WheelButton: KeySym = KeySym(0x1008FF88); -pub const SYM_XF86Word: KeySym = KeySym(0x1008FF89); -pub const SYM_XF86Xfer: KeySym = KeySym(0x1008FF8A); -pub const SYM_XF86ZoomIn: KeySym = KeySym(0x1008FF8B); -pub const SYM_XF86ZoomOut: KeySym = KeySym(0x1008FF8C); -pub const SYM_XF86Away: KeySym = KeySym(0x1008FF8D); -pub const SYM_XF86Messenger: KeySym = KeySym(0x1008FF8E); -pub const SYM_XF86WebCam: KeySym = KeySym(0x1008FF8F); -pub const SYM_XF86MailForward: KeySym = KeySym(0x1008FF90); -pub const SYM_XF86Pictures: KeySym = KeySym(0x1008FF91); -pub const SYM_XF86Music: KeySym = KeySym(0x1008FF92); -pub const SYM_XF86Battery: KeySym = KeySym(0x1008FF93); -pub const SYM_XF86Bluetooth: KeySym = KeySym(0x1008FF94); -pub const SYM_XF86WLAN: KeySym = KeySym(0x1008FF95); -pub const SYM_XF86UWB: KeySym = KeySym(0x1008FF96); -pub const SYM_XF86AudioForward: KeySym = KeySym(0x1008FF97); -pub const SYM_XF86AudioRepeat: KeySym = KeySym(0x1008FF98); -pub const SYM_XF86AudioRandomPlay: KeySym = KeySym(0x1008FF99); -pub const SYM_XF86Subtitle: KeySym = KeySym(0x1008FF9A); -pub const SYM_XF86AudioCycleTrack: KeySym = KeySym(0x1008FF9B); -pub const SYM_XF86CycleAngle: KeySym = KeySym(0x1008FF9C); -pub const SYM_XF86FrameBack: KeySym = KeySym(0x1008FF9D); -pub const SYM_XF86FrameForward: KeySym = KeySym(0x1008FF9E); -pub const SYM_XF86Time: KeySym = KeySym(0x1008FF9F); -pub const SYM_XF86Select: KeySym = KeySym(0x1008FFA0); -pub const SYM_XF86View: KeySym = KeySym(0x1008FFA1); -pub const SYM_XF86TopMenu: KeySym = KeySym(0x1008FFA2); -pub const SYM_XF86Red: KeySym = KeySym(0x1008FFA3); -pub const SYM_XF86Green: KeySym = KeySym(0x1008FFA4); -pub const SYM_XF86Yellow: KeySym = KeySym(0x1008FFA5); -pub const SYM_XF86Blue: KeySym = KeySym(0x1008FFA6); -pub const SYM_XF86Suspend: KeySym = KeySym(0x1008FFA7); -pub const SYM_XF86Hibernate: KeySym = KeySym(0x1008FFA8); -pub const SYM_XF86TouchpadToggle: KeySym = KeySym(0x1008FFA9); -pub const SYM_XF86TouchpadOn: KeySym = KeySym(0x1008FFB0); -pub const SYM_XF86TouchpadOff: KeySym = KeySym(0x1008FFB1); -pub const SYM_XF86AudioMicMute: KeySym = KeySym(0x1008FFB2); -pub const SYM_XF86Keyboard: KeySym = KeySym(0x1008FFB3); -pub const SYM_XF86WWAN: KeySym = KeySym(0x1008FFB4); -pub const SYM_XF86RFKill: KeySym = KeySym(0x1008FFB5); -pub const SYM_XF86AudioPreset: KeySym = KeySym(0x1008FFB6); -pub const SYM_XF86RotationLockToggle: KeySym = KeySym(0x1008FFB7); -pub const SYM_XF86FullScreen: KeySym = KeySym(0x1008FFB8); -pub const SYM_XF86Switch_VT_1: KeySym = KeySym(0x1008FE01); -pub const SYM_XF86Switch_VT_2: KeySym = KeySym(0x1008FE02); -pub const SYM_XF86Switch_VT_3: KeySym = KeySym(0x1008FE03); -pub const SYM_XF86Switch_VT_4: KeySym = KeySym(0x1008FE04); -pub const SYM_XF86Switch_VT_5: KeySym = KeySym(0x1008FE05); -pub const SYM_XF86Switch_VT_6: KeySym = KeySym(0x1008FE06); -pub const SYM_XF86Switch_VT_7: KeySym = KeySym(0x1008FE07); -pub const SYM_XF86Switch_VT_8: KeySym = KeySym(0x1008FE08); -pub const SYM_XF86Switch_VT_9: KeySym = KeySym(0x1008FE09); -pub const SYM_XF86Switch_VT_10: KeySym = KeySym(0x1008FE0A); -pub const SYM_XF86Switch_VT_11: KeySym = KeySym(0x1008FE0B); -pub const SYM_XF86Switch_VT_12: KeySym = KeySym(0x1008FE0C); -pub const SYM_XF86Ungrab: KeySym = KeySym(0x1008FE20); -pub const SYM_XF86ClearGrab: KeySym = KeySym(0x1008FE21); -pub const SYM_XF86Next_VMode: KeySym = KeySym(0x1008FE22); -pub const SYM_XF86Prev_VMode: KeySym = KeySym(0x1008FE23); -pub const SYM_XF86LogWindowTree: KeySym = KeySym(0x1008FE24); -pub const SYM_XF86LogGrabInfo: KeySym = KeySym(0x1008FE25); +pub const SYM_XF86ModeLock: KeySym = KeySym(0x1008ff01); +pub const SYM_XF86MonBrightnessUp: KeySym = KeySym(0x1008ff02); +pub const SYM_XF86MonBrightnessDown: KeySym = KeySym(0x1008ff03); +pub const SYM_XF86KbdLightOnOff: KeySym = KeySym(0x1008ff04); +pub const SYM_XF86KbdBrightnessUp: KeySym = KeySym(0x1008ff05); +pub const SYM_XF86KbdBrightnessDown: KeySym = KeySym(0x1008ff06); +pub const SYM_XF86MonBrightnessCycle: KeySym = KeySym(0x1008ff07); +pub const SYM_XF86Standby: KeySym = KeySym(0x1008ff10); +pub const SYM_XF86AudioLowerVolume: KeySym = KeySym(0x1008ff11); +pub const SYM_XF86AudioMute: KeySym = KeySym(0x1008ff12); +pub const SYM_XF86AudioRaiseVolume: KeySym = KeySym(0x1008ff13); +pub const SYM_XF86AudioPlay: KeySym = KeySym(0x1008ff14); +pub const SYM_XF86AudioStop: KeySym = KeySym(0x1008ff15); +pub const SYM_XF86AudioPrev: KeySym = KeySym(0x1008ff16); +pub const SYM_XF86AudioNext: KeySym = KeySym(0x1008ff17); +pub const SYM_XF86HomePage: KeySym = KeySym(0x1008ff18); +pub const SYM_XF86Mail: KeySym = KeySym(0x1008ff19); +pub const SYM_XF86Start: KeySym = KeySym(0x1008ff1a); +pub const SYM_XF86Search: KeySym = KeySym(0x1008ff1b); +pub const SYM_XF86AudioRecord: KeySym = KeySym(0x1008ff1c); +pub const SYM_XF86Calculator: KeySym = KeySym(0x1008ff1d); +pub const SYM_XF86Memo: KeySym = KeySym(0x1008ff1e); +pub const SYM_XF86ToDoList: KeySym = KeySym(0x1008ff1f); +pub const SYM_XF86Calendar: KeySym = KeySym(0x1008ff20); +pub const SYM_XF86PowerDown: KeySym = KeySym(0x1008ff21); +pub const SYM_XF86ContrastAdjust: KeySym = KeySym(0x1008ff22); +pub const SYM_XF86RockerUp: KeySym = KeySym(0x1008ff23); +pub const SYM_XF86RockerDown: KeySym = KeySym(0x1008ff24); +pub const SYM_XF86RockerEnter: KeySym = KeySym(0x1008ff25); +pub const SYM_XF86Back: KeySym = KeySym(0x1008ff26); +pub const SYM_XF86Forward: KeySym = KeySym(0x1008ff27); +pub const SYM_XF86Stop: KeySym = KeySym(0x1008ff28); +pub const SYM_XF86Refresh: KeySym = KeySym(0x1008ff29); +pub const SYM_XF86PowerOff: KeySym = KeySym(0x1008ff2a); +pub const SYM_XF86WakeUp: KeySym = KeySym(0x1008ff2b); +pub const SYM_XF86Eject: KeySym = KeySym(0x1008ff2c); +pub const SYM_XF86ScreenSaver: KeySym = KeySym(0x1008ff2d); +pub const SYM_XF86WWW: KeySym = KeySym(0x1008ff2e); +pub const SYM_XF86Sleep: KeySym = KeySym(0x1008ff2f); +pub const SYM_XF86Favorites: KeySym = KeySym(0x1008ff30); +pub const SYM_XF86AudioPause: KeySym = KeySym(0x1008ff31); +pub const SYM_XF86AudioMedia: KeySym = KeySym(0x1008ff32); +pub const SYM_XF86MyComputer: KeySym = KeySym(0x1008ff33); +pub const SYM_XF86VendorHome: KeySym = KeySym(0x1008ff34); +pub const SYM_XF86LightBulb: KeySym = KeySym(0x1008ff35); +pub const SYM_XF86Shop: KeySym = KeySym(0x1008ff36); +pub const SYM_XF86History: KeySym = KeySym(0x1008ff37); +pub const SYM_XF86OpenURL: KeySym = KeySym(0x1008ff38); +pub const SYM_XF86AddFavorite: KeySym = KeySym(0x1008ff39); +pub const SYM_XF86HotLinks: KeySym = KeySym(0x1008ff3a); +pub const SYM_XF86BrightnessAdjust: KeySym = KeySym(0x1008ff3b); +pub const SYM_XF86Finance: KeySym = KeySym(0x1008ff3c); +pub const SYM_XF86Community: KeySym = KeySym(0x1008ff3d); +pub const SYM_XF86AudioRewind: KeySym = KeySym(0x1008ff3e); +pub const SYM_XF86BackForward: KeySym = KeySym(0x1008ff3f); +pub const SYM_XF86Launch0: KeySym = KeySym(0x1008ff40); +pub const SYM_XF86Launch1: KeySym = KeySym(0x1008ff41); +pub const SYM_XF86Launch2: KeySym = KeySym(0x1008ff42); +pub const SYM_XF86Launch3: KeySym = KeySym(0x1008ff43); +pub const SYM_XF86Launch4: KeySym = KeySym(0x1008ff44); +pub const SYM_XF86Launch5: KeySym = KeySym(0x1008ff45); +pub const SYM_XF86Launch6: KeySym = KeySym(0x1008ff46); +pub const SYM_XF86Launch7: KeySym = KeySym(0x1008ff47); +pub const SYM_XF86Launch8: KeySym = KeySym(0x1008ff48); +pub const SYM_XF86Launch9: KeySym = KeySym(0x1008ff49); +pub const SYM_XF86LaunchA: KeySym = KeySym(0x1008ff4a); +pub const SYM_XF86LaunchB: KeySym = KeySym(0x1008ff4b); +pub const SYM_XF86LaunchC: KeySym = KeySym(0x1008ff4c); +pub const SYM_XF86LaunchD: KeySym = KeySym(0x1008ff4d); +pub const SYM_XF86LaunchE: KeySym = KeySym(0x1008ff4e); +pub const SYM_XF86LaunchF: KeySym = KeySym(0x1008ff4f); +pub const SYM_XF86ApplicationLeft: KeySym = KeySym(0x1008ff50); +pub const SYM_XF86ApplicationRight: KeySym = KeySym(0x1008ff51); +pub const SYM_XF86Book: KeySym = KeySym(0x1008ff52); +pub const SYM_XF86CD: KeySym = KeySym(0x1008ff53); +pub const SYM_XF86Calculater: KeySym = KeySym(0x1008ff54); +pub const SYM_XF86Clear: KeySym = KeySym(0x1008ff55); +pub const SYM_XF86Close: KeySym = KeySym(0x1008ff56); +pub const SYM_XF86Copy: KeySym = KeySym(0x1008ff57); +pub const SYM_XF86Cut: KeySym = KeySym(0x1008ff58); +pub const SYM_XF86Display: KeySym = KeySym(0x1008ff59); +pub const SYM_XF86DOS: KeySym = KeySym(0x1008ff5a); +pub const SYM_XF86Documents: KeySym = KeySym(0x1008ff5b); +pub const SYM_XF86Excel: KeySym = KeySym(0x1008ff5c); +pub const SYM_XF86Explorer: KeySym = KeySym(0x1008ff5d); +pub const SYM_XF86Game: KeySym = KeySym(0x1008ff5e); +pub const SYM_XF86Go: KeySym = KeySym(0x1008ff5f); +pub const SYM_XF86iTouch: KeySym = KeySym(0x1008ff60); +pub const SYM_XF86LogOff: KeySym = KeySym(0x1008ff61); +pub const SYM_XF86Market: KeySym = KeySym(0x1008ff62); +pub const SYM_XF86Meeting: KeySym = KeySym(0x1008ff63); +pub const SYM_XF86MenuKB: KeySym = KeySym(0x1008ff65); +pub const SYM_XF86MenuPB: KeySym = KeySym(0x1008ff66); +pub const SYM_XF86MySites: KeySym = KeySym(0x1008ff67); +pub const SYM_XF86New: KeySym = KeySym(0x1008ff68); +pub const SYM_XF86News: KeySym = KeySym(0x1008ff69); +pub const SYM_XF86OfficeHome: KeySym = KeySym(0x1008ff6a); +pub const SYM_XF86Open: KeySym = KeySym(0x1008ff6b); +pub const SYM_XF86Option: KeySym = KeySym(0x1008ff6c); +pub const SYM_XF86Paste: KeySym = KeySym(0x1008ff6d); +pub const SYM_XF86Phone: KeySym = KeySym(0x1008ff6e); +pub const SYM_XF86Q: KeySym = KeySym(0x1008ff70); +pub const SYM_XF86Reply: KeySym = KeySym(0x1008ff72); +pub const SYM_XF86Reload: KeySym = KeySym(0x1008ff73); +pub const SYM_XF86RotateWindows: KeySym = KeySym(0x1008ff74); +pub const SYM_XF86RotationPB: KeySym = KeySym(0x1008ff75); +pub const SYM_XF86RotationKB: KeySym = KeySym(0x1008ff76); +pub const SYM_XF86Save: KeySym = KeySym(0x1008ff77); +pub const SYM_XF86ScrollUp: KeySym = KeySym(0x1008ff78); +pub const SYM_XF86ScrollDown: KeySym = KeySym(0x1008ff79); +pub const SYM_XF86ScrollClick: KeySym = KeySym(0x1008ff7a); +pub const SYM_XF86Send: KeySym = KeySym(0x1008ff7b); +pub const SYM_XF86Spell: KeySym = KeySym(0x1008ff7c); +pub const SYM_XF86SplitScreen: KeySym = KeySym(0x1008ff7d); +pub const SYM_XF86Support: KeySym = KeySym(0x1008ff7e); +pub const SYM_XF86TaskPane: KeySym = KeySym(0x1008ff7f); +pub const SYM_XF86Terminal: KeySym = KeySym(0x1008ff80); +pub const SYM_XF86Tools: KeySym = KeySym(0x1008ff81); +pub const SYM_XF86Travel: KeySym = KeySym(0x1008ff82); +pub const SYM_XF86UserPB: KeySym = KeySym(0x1008ff84); +pub const SYM_XF86User1KB: KeySym = KeySym(0x1008ff85); +pub const SYM_XF86User2KB: KeySym = KeySym(0x1008ff86); +pub const SYM_XF86Video: KeySym = KeySym(0x1008ff87); +pub const SYM_XF86WheelButton: KeySym = KeySym(0x1008ff88); +pub const SYM_XF86Word: KeySym = KeySym(0x1008ff89); +pub const SYM_XF86Xfer: KeySym = KeySym(0x1008ff8a); +pub const SYM_XF86ZoomIn: KeySym = KeySym(0x1008ff8b); +pub const SYM_XF86ZoomOut: KeySym = KeySym(0x1008ff8c); +pub const SYM_XF86Away: KeySym = KeySym(0x1008ff8d); +pub const SYM_XF86Messenger: KeySym = KeySym(0x1008ff8e); +pub const SYM_XF86WebCam: KeySym = KeySym(0x1008ff8f); +pub const SYM_XF86MailForward: KeySym = KeySym(0x1008ff90); +pub const SYM_XF86Pictures: KeySym = KeySym(0x1008ff91); +pub const SYM_XF86Music: KeySym = KeySym(0x1008ff92); +pub const SYM_XF86Battery: KeySym = KeySym(0x1008ff93); +pub const SYM_XF86Bluetooth: KeySym = KeySym(0x1008ff94); +pub const SYM_XF86WLAN: KeySym = KeySym(0x1008ff95); +pub const SYM_XF86UWB: KeySym = KeySym(0x1008ff96); +pub const SYM_XF86AudioForward: KeySym = KeySym(0x1008ff97); +pub const SYM_XF86AudioRepeat: KeySym = KeySym(0x1008ff98); +pub const SYM_XF86AudioRandomPlay: KeySym = KeySym(0x1008ff99); +pub const SYM_XF86Subtitle: KeySym = KeySym(0x1008ff9a); +pub const SYM_XF86AudioCycleTrack: KeySym = KeySym(0x1008ff9b); +pub const SYM_XF86CycleAngle: KeySym = KeySym(0x1008ff9c); +pub const SYM_XF86FrameBack: KeySym = KeySym(0x1008ff9d); +pub const SYM_XF86FrameForward: KeySym = KeySym(0x1008ff9e); +pub const SYM_XF86Time: KeySym = KeySym(0x1008ff9f); +pub const SYM_XF86Select: KeySym = KeySym(0x1008ffa0); +pub const SYM_XF86View: KeySym = KeySym(0x1008ffa1); +pub const SYM_XF86TopMenu: KeySym = KeySym(0x1008ffa2); +pub const SYM_XF86Red: KeySym = KeySym(0x1008ffa3); +pub const SYM_XF86Green: KeySym = KeySym(0x1008ffa4); +pub const SYM_XF86Yellow: KeySym = KeySym(0x1008ffa5); +pub const SYM_XF86Blue: KeySym = KeySym(0x1008ffa6); +pub const SYM_XF86Suspend: KeySym = KeySym(0x1008ffa7); +pub const SYM_XF86Hibernate: KeySym = KeySym(0x1008ffa8); +pub const SYM_XF86TouchpadToggle: KeySym = KeySym(0x1008ffa9); +pub const SYM_XF86TouchpadOn: KeySym = KeySym(0x1008ffb0); +pub const SYM_XF86TouchpadOff: KeySym = KeySym(0x1008ffb1); +pub const SYM_XF86AudioMicMute: KeySym = KeySym(0x1008ffb2); +pub const SYM_XF86Keyboard: KeySym = KeySym(0x1008ffb3); +pub const SYM_XF86WWAN: KeySym = KeySym(0x1008ffb4); +pub const SYM_XF86RFKill: KeySym = KeySym(0x1008ffb5); +pub const SYM_XF86AudioPreset: KeySym = KeySym(0x1008ffb6); +pub const SYM_XF86RotationLockToggle: KeySym = KeySym(0x1008ffb7); +pub const SYM_XF86FullScreen: KeySym = KeySym(0x1008ffb8); +pub const SYM_XF86Switch_VT_1: KeySym = KeySym(0x1008fe01); +pub const SYM_XF86Switch_VT_2: KeySym = KeySym(0x1008fe02); +pub const SYM_XF86Switch_VT_3: KeySym = KeySym(0x1008fe03); +pub const SYM_XF86Switch_VT_4: KeySym = KeySym(0x1008fe04); +pub const SYM_XF86Switch_VT_5: KeySym = KeySym(0x1008fe05); +pub const SYM_XF86Switch_VT_6: KeySym = KeySym(0x1008fe06); +pub const SYM_XF86Switch_VT_7: KeySym = KeySym(0x1008fe07); +pub const SYM_XF86Switch_VT_8: KeySym = KeySym(0x1008fe08); +pub const SYM_XF86Switch_VT_9: KeySym = KeySym(0x1008fe09); +pub const SYM_XF86Switch_VT_10: KeySym = KeySym(0x1008fe0a); +pub const SYM_XF86Switch_VT_11: KeySym = KeySym(0x1008fe0b); +pub const SYM_XF86Switch_VT_12: KeySym = KeySym(0x1008fe0c); +pub const SYM_XF86Ungrab: KeySym = KeySym(0x1008fe20); +pub const SYM_XF86ClearGrab: KeySym = KeySym(0x1008fe21); +pub const SYM_XF86Next_VMode: KeySym = KeySym(0x1008fe22); +pub const SYM_XF86Prev_VMode: KeySym = KeySym(0x1008fe23); +pub const SYM_XF86LogWindowTree: KeySym = KeySym(0x1008fe24); +pub const SYM_XF86LogGrabInfo: KeySym = KeySym(0x1008fe25); pub const SYM_XF86BrightnessAuto: KeySym = KeySym(0x100810f4); pub const SYM_XF86DisplayOff: KeySym = KeySym(0x100810f5); pub const SYM_XF86Info: KeySym = KeySym(0x10081166); @@ -2362,6 +2370,11 @@ pub const SYM_XF86AppSelect: KeySym = KeySym(0x10081244); pub const SYM_XF86Screensaver: KeySym = KeySym(0x10081245); pub const SYM_XF86VoiceCommand: KeySym = KeySym(0x10081246); pub const SYM_XF86Assistant: KeySym = KeySym(0x10081247); +pub const SYM_XF86EmojiPicker: KeySym = KeySym(0x10081249); +pub const SYM_XF86Dictate: KeySym = KeySym(0x1008124a); +pub const SYM_XF86CameraAccessEnable: KeySym = KeySym(0x1008124b); +pub const SYM_XF86CameraAccessDisable: KeySym = KeySym(0x1008124c); +pub const SYM_XF86CameraAccessToggle: KeySym = KeySym(0x1008124d); pub const SYM_XF86BrightnessMin: KeySym = KeySym(0x10081250); pub const SYM_XF86BrightnessMax: KeySym = KeySym(0x10081251); pub const SYM_XF86KbdInputAssistPrev: KeySym = KeySym(0x10081260); @@ -2391,6 +2404,20 @@ pub const SYM_XF86Data: KeySym = KeySym(0x10081277); pub const SYM_XF86OnScreenKeyboard: KeySym = KeySym(0x10081278); pub const SYM_XF86PrivacyScreenToggle: KeySym = KeySym(0x10081279); pub const SYM_XF86SelectiveScreenshot: KeySym = KeySym(0x1008127a); +pub const SYM_XF86NextElement: KeySym = KeySym(0x1008127b); +pub const SYM_XF86PreviousElement: KeySym = KeySym(0x1008127c); +pub const SYM_XF86AutopilotEngageToggle: KeySym = KeySym(0x1008127d); +pub const SYM_XF86MarkWaypoint: KeySym = KeySym(0x1008127e); +pub const SYM_XF86Sos: KeySym = KeySym(0x1008127f); +pub const SYM_XF86NavChart: KeySym = KeySym(0x10081280); +pub const SYM_XF86FishingChart: KeySym = KeySym(0x10081281); +pub const SYM_XF86SingleRangeRadar: KeySym = KeySym(0x10081282); +pub const SYM_XF86DualRangeRadar: KeySym = KeySym(0x10081283); +pub const SYM_XF86RadarOverlay: KeySym = KeySym(0x10081284); +pub const SYM_XF86TraditionalSonar: KeySym = KeySym(0x10081285); +pub const SYM_XF86ClearvuSonar: KeySym = KeySym(0x10081286); +pub const SYM_XF86SidevuSonar: KeySym = KeySym(0x10081287); +pub const SYM_XF86NavInfo: KeySym = KeySym(0x10081288); pub const SYM_XF86Macro1: KeySym = KeySym(0x10081290); pub const SYM_XF86Macro2: KeySym = KeySym(0x10081291); pub const SYM_XF86Macro3: KeySym = KeySym(0x10081292); @@ -2432,121 +2459,121 @@ pub const SYM_XF86KbdLcdMenu2: KeySym = KeySym(0x100812b9); pub const SYM_XF86KbdLcdMenu3: KeySym = KeySym(0x100812ba); pub const SYM_XF86KbdLcdMenu4: KeySym = KeySym(0x100812bb); pub const SYM_XF86KbdLcdMenu5: KeySym = KeySym(0x100812bc); -pub const SYM_SunFA_Grave: KeySym = KeySym(0x1005FF00); -pub const SYM_SunFA_Circum: KeySym = KeySym(0x1005FF01); -pub const SYM_SunFA_Tilde: KeySym = KeySym(0x1005FF02); -pub const SYM_SunFA_Acute: KeySym = KeySym(0x1005FF03); -pub const SYM_SunFA_Diaeresis: KeySym = KeySym(0x1005FF04); -pub const SYM_SunFA_Cedilla: KeySym = KeySym(0x1005FF05); -pub const SYM_SunF36: KeySym = KeySym(0x1005FF10); -pub const SYM_SunF37: KeySym = KeySym(0x1005FF11); -pub const SYM_SunSys_Req: KeySym = KeySym(0x1005FF60); -pub const SYM_SunPrint_Screen: KeySym = KeySym(0x0000FF61); -pub const SYM_SunCompose: KeySym = KeySym(0x0000FF20); -pub const SYM_SunAltGraph: KeySym = KeySym(0x0000FF7E); -pub const SYM_SunPageUp: KeySym = KeySym(0x0000FF55); -pub const SYM_SunPageDown: KeySym = KeySym(0x0000FF56); -pub const SYM_SunUndo: KeySym = KeySym(0x0000FF65); -pub const SYM_SunAgain: KeySym = KeySym(0x0000FF66); -pub const SYM_SunFind: KeySym = KeySym(0x0000FF68); -pub const SYM_SunStop: KeySym = KeySym(0x0000FF69); -pub const SYM_SunProps: KeySym = KeySym(0x1005FF70); -pub const SYM_SunFront: KeySym = KeySym(0x1005FF71); -pub const SYM_SunCopy: KeySym = KeySym(0x1005FF72); -pub const SYM_SunOpen: KeySym = KeySym(0x1005FF73); -pub const SYM_SunPaste: KeySym = KeySym(0x1005FF74); -pub const SYM_SunCut: KeySym = KeySym(0x1005FF75); -pub const SYM_SunPowerSwitch: KeySym = KeySym(0x1005FF76); -pub const SYM_SunAudioLowerVolume: KeySym = KeySym(0x1005FF77); -pub const SYM_SunAudioMute: KeySym = KeySym(0x1005FF78); -pub const SYM_SunAudioRaiseVolume: KeySym = KeySym(0x1005FF79); -pub const SYM_SunVideoDegauss: KeySym = KeySym(0x1005FF7A); -pub const SYM_SunVideoLowerBrightness: KeySym = KeySym(0x1005FF7B); -pub const SYM_SunVideoRaiseBrightness: KeySym = KeySym(0x1005FF7C); -pub const SYM_SunPowerSwitchShift: KeySym = KeySym(0x1005FF7D); -pub const SYM_Dring_accent: KeySym = KeySym(0x1000FEB0); -pub const SYM_Dcircumflex_accent: KeySym = KeySym(0x1000FE5E); -pub const SYM_Dcedilla_accent: KeySym = KeySym(0x1000FE2C); -pub const SYM_Dacute_accent: KeySym = KeySym(0x1000FE27); -pub const SYM_Dgrave_accent: KeySym = KeySym(0x1000FE60); -pub const SYM_Dtilde: KeySym = KeySym(0x1000FE7E); -pub const SYM_Ddiaeresis: KeySym = KeySym(0x1000FE22); -pub const SYM_DRemove: KeySym = KeySym(0x1000FF00); -pub const SYM_hpClearLine: KeySym = KeySym(0x1000FF6F); -pub const SYM_hpInsertLine: KeySym = KeySym(0x1000FF70); -pub const SYM_hpDeleteLine: KeySym = KeySym(0x1000FF71); -pub const SYM_hpInsertChar: KeySym = KeySym(0x1000FF72); -pub const SYM_hpDeleteChar: KeySym = KeySym(0x1000FF73); -pub const SYM_hpBackTab: KeySym = KeySym(0x1000FF74); -pub const SYM_hpKP_BackTab: KeySym = KeySym(0x1000FF75); -pub const SYM_hpModelock1: KeySym = KeySym(0x1000FF48); -pub const SYM_hpModelock2: KeySym = KeySym(0x1000FF49); -pub const SYM_hpReset: KeySym = KeySym(0x1000FF6C); -pub const SYM_hpSystem: KeySym = KeySym(0x1000FF6D); -pub const SYM_hpUser: KeySym = KeySym(0x1000FF6E); -pub const SYM_hpmute_acute: KeySym = KeySym(0x100000A8); -pub const SYM_hpmute_grave: KeySym = KeySym(0x100000A9); -pub const SYM_hpmute_asciicircum: KeySym = KeySym(0x100000AA); -pub const SYM_hpmute_diaeresis: KeySym = KeySym(0x100000AB); -pub const SYM_hpmute_asciitilde: KeySym = KeySym(0x100000AC); -pub const SYM_hplira: KeySym = KeySym(0x100000AF); -pub const SYM_hpguilder: KeySym = KeySym(0x100000BE); -pub const SYM_hpYdiaeresis: KeySym = KeySym(0x100000EE); -pub const SYM_hpIO: KeySym = KeySym(0x100000EE); -pub const SYM_hplongminus: KeySym = KeySym(0x100000F6); -pub const SYM_hpblock: KeySym = KeySym(0x100000FC); -pub const SYM_osfCopy: KeySym = KeySym(0x1004FF02); -pub const SYM_osfCut: KeySym = KeySym(0x1004FF03); -pub const SYM_osfPaste: KeySym = KeySym(0x1004FF04); -pub const SYM_osfBackTab: KeySym = KeySym(0x1004FF07); -pub const SYM_osfBackSpace: KeySym = KeySym(0x1004FF08); -pub const SYM_osfClear: KeySym = KeySym(0x1004FF0B); -pub const SYM_osfEscape: KeySym = KeySym(0x1004FF1B); -pub const SYM_osfAddMode: KeySym = KeySym(0x1004FF31); -pub const SYM_osfPrimaryPaste: KeySym = KeySym(0x1004FF32); -pub const SYM_osfQuickPaste: KeySym = KeySym(0x1004FF33); -pub const SYM_osfPageLeft: KeySym = KeySym(0x1004FF40); -pub const SYM_osfPageUp: KeySym = KeySym(0x1004FF41); -pub const SYM_osfPageDown: KeySym = KeySym(0x1004FF42); -pub const SYM_osfPageRight: KeySym = KeySym(0x1004FF43); -pub const SYM_osfActivate: KeySym = KeySym(0x1004FF44); -pub const SYM_osfMenuBar: KeySym = KeySym(0x1004FF45); -pub const SYM_osfLeft: KeySym = KeySym(0x1004FF51); -pub const SYM_osfUp: KeySym = KeySym(0x1004FF52); -pub const SYM_osfRight: KeySym = KeySym(0x1004FF53); -pub const SYM_osfDown: KeySym = KeySym(0x1004FF54); -pub const SYM_osfEndLine: KeySym = KeySym(0x1004FF57); -pub const SYM_osfBeginLine: KeySym = KeySym(0x1004FF58); -pub const SYM_osfEndData: KeySym = KeySym(0x1004FF59); -pub const SYM_osfBeginData: KeySym = KeySym(0x1004FF5A); -pub const SYM_osfPrevMenu: KeySym = KeySym(0x1004FF5B); -pub const SYM_osfNextMenu: KeySym = KeySym(0x1004FF5C); -pub const SYM_osfPrevField: KeySym = KeySym(0x1004FF5D); -pub const SYM_osfNextField: KeySym = KeySym(0x1004FF5E); -pub const SYM_osfSelect: KeySym = KeySym(0x1004FF60); -pub const SYM_osfInsert: KeySym = KeySym(0x1004FF63); -pub const SYM_osfUndo: KeySym = KeySym(0x1004FF65); -pub const SYM_osfMenu: KeySym = KeySym(0x1004FF67); -pub const SYM_osfCancel: KeySym = KeySym(0x1004FF69); -pub const SYM_osfHelp: KeySym = KeySym(0x1004FF6A); -pub const SYM_osfSelectAll: KeySym = KeySym(0x1004FF71); -pub const SYM_osfDeselectAll: KeySym = KeySym(0x1004FF72); -pub const SYM_osfReselect: KeySym = KeySym(0x1004FF73); -pub const SYM_osfExtend: KeySym = KeySym(0x1004FF74); -pub const SYM_osfRestore: KeySym = KeySym(0x1004FF78); -pub const SYM_osfDelete: KeySym = KeySym(0x1004FFFF); -pub const SYM_Reset: KeySym = KeySym(0x1000FF6C); -pub const SYM_System: KeySym = KeySym(0x1000FF6D); -pub const SYM_User: KeySym = KeySym(0x1000FF6E); -pub const SYM_ClearLine: KeySym = KeySym(0x1000FF6F); -pub const SYM_InsertLine: KeySym = KeySym(0x1000FF70); -pub const SYM_DeleteLine: KeySym = KeySym(0x1000FF71); -pub const SYM_InsertChar: KeySym = KeySym(0x1000FF72); -pub const SYM_DeleteChar: KeySym = KeySym(0x1000FF73); -pub const SYM_BackTab: KeySym = KeySym(0x1000FF74); -pub const SYM_KP_BackTab: KeySym = KeySym(0x1000FF75); -pub const SYM_Ext16bit_L: KeySym = KeySym(0x1000FF76); -pub const SYM_Ext16bit_R: KeySym = KeySym(0x1000FF77); +pub const SYM_SunFA_Grave: KeySym = KeySym(0x1005ff00); +pub const SYM_SunFA_Circum: KeySym = KeySym(0x1005ff01); +pub const SYM_SunFA_Tilde: KeySym = KeySym(0x1005ff02); +pub const SYM_SunFA_Acute: KeySym = KeySym(0x1005ff03); +pub const SYM_SunFA_Diaeresis: KeySym = KeySym(0x1005ff04); +pub const SYM_SunFA_Cedilla: KeySym = KeySym(0x1005ff05); +pub const SYM_SunF36: KeySym = KeySym(0x1005ff10); +pub const SYM_SunF37: KeySym = KeySym(0x1005ff11); +pub const SYM_SunSys_Req: KeySym = KeySym(0x1005ff60); +pub const SYM_SunPrint_Screen: KeySym = KeySym(0x0000ff61); +pub const SYM_SunCompose: KeySym = KeySym(0x0000ff20); +pub const SYM_SunAltGraph: KeySym = KeySym(0x0000ff7e); +pub const SYM_SunPageUp: KeySym = KeySym(0x0000ff55); +pub const SYM_SunPageDown: KeySym = KeySym(0x0000ff56); +pub const SYM_SunUndo: KeySym = KeySym(0x0000ff65); +pub const SYM_SunAgain: KeySym = KeySym(0x0000ff66); +pub const SYM_SunFind: KeySym = KeySym(0x0000ff68); +pub const SYM_SunStop: KeySym = KeySym(0x0000ff69); +pub const SYM_SunProps: KeySym = KeySym(0x1005ff70); +pub const SYM_SunFront: KeySym = KeySym(0x1005ff71); +pub const SYM_SunCopy: KeySym = KeySym(0x1005ff72); +pub const SYM_SunOpen: KeySym = KeySym(0x1005ff73); +pub const SYM_SunPaste: KeySym = KeySym(0x1005ff74); +pub const SYM_SunCut: KeySym = KeySym(0x1005ff75); +pub const SYM_SunPowerSwitch: KeySym = KeySym(0x1005ff76); +pub const SYM_SunAudioLowerVolume: KeySym = KeySym(0x1005ff77); +pub const SYM_SunAudioMute: KeySym = KeySym(0x1005ff78); +pub const SYM_SunAudioRaiseVolume: KeySym = KeySym(0x1005ff79); +pub const SYM_SunVideoDegauss: KeySym = KeySym(0x1005ff7a); +pub const SYM_SunVideoLowerBrightness: KeySym = KeySym(0x1005ff7b); +pub const SYM_SunVideoRaiseBrightness: KeySym = KeySym(0x1005ff7c); +pub const SYM_SunPowerSwitchShift: KeySym = KeySym(0x1005ff7d); +pub const SYM_Dring_accent: KeySym = KeySym(0x1000feb0); +pub const SYM_Dcircumflex_accent: KeySym = KeySym(0x1000fe5e); +pub const SYM_Dcedilla_accent: KeySym = KeySym(0x1000fe2c); +pub const SYM_Dacute_accent: KeySym = KeySym(0x1000fe27); +pub const SYM_Dgrave_accent: KeySym = KeySym(0x1000fe60); +pub const SYM_Dtilde: KeySym = KeySym(0x1000fe7e); +pub const SYM_Ddiaeresis: KeySym = KeySym(0x1000fe22); +pub const SYM_DRemove: KeySym = KeySym(0x1000ff00); +pub const SYM_hpClearLine: KeySym = KeySym(0x1000ff6f); +pub const SYM_hpInsertLine: KeySym = KeySym(0x1000ff70); +pub const SYM_hpDeleteLine: KeySym = KeySym(0x1000ff71); +pub const SYM_hpInsertChar: KeySym = KeySym(0x1000ff72); +pub const SYM_hpDeleteChar: KeySym = KeySym(0x1000ff73); +pub const SYM_hpBackTab: KeySym = KeySym(0x1000ff74); +pub const SYM_hpKP_BackTab: KeySym = KeySym(0x1000ff75); +pub const SYM_hpModelock1: KeySym = KeySym(0x1000ff48); +pub const SYM_hpModelock2: KeySym = KeySym(0x1000ff49); +pub const SYM_hpReset: KeySym = KeySym(0x1000ff6c); +pub const SYM_hpSystem: KeySym = KeySym(0x1000ff6d); +pub const SYM_hpUser: KeySym = KeySym(0x1000ff6e); +pub const SYM_hpmute_acute: KeySym = KeySym(0x100000a8); +pub const SYM_hpmute_grave: KeySym = KeySym(0x100000a9); +pub const SYM_hpmute_asciicircum: KeySym = KeySym(0x100000aa); +pub const SYM_hpmute_diaeresis: KeySym = KeySym(0x100000ab); +pub const SYM_hpmute_asciitilde: KeySym = KeySym(0x100000ac); +pub const SYM_hplira: KeySym = KeySym(0x100000af); +pub const SYM_hpguilder: KeySym = KeySym(0x100000be); +pub const SYM_hpYdiaeresis: KeySym = KeySym(0x100000ee); +pub const SYM_hpIO: KeySym = KeySym(0x100000ee); +pub const SYM_hplongminus: KeySym = KeySym(0x100000f6); +pub const SYM_hpblock: KeySym = KeySym(0x100000fc); +pub const SYM_osfCopy: KeySym = KeySym(0x1004ff02); +pub const SYM_osfCut: KeySym = KeySym(0x1004ff03); +pub const SYM_osfPaste: KeySym = KeySym(0x1004ff04); +pub const SYM_osfBackTab: KeySym = KeySym(0x1004ff07); +pub const SYM_osfBackSpace: KeySym = KeySym(0x1004ff08); +pub const SYM_osfClear: KeySym = KeySym(0x1004ff0b); +pub const SYM_osfEscape: KeySym = KeySym(0x1004ff1b); +pub const SYM_osfAddMode: KeySym = KeySym(0x1004ff31); +pub const SYM_osfPrimaryPaste: KeySym = KeySym(0x1004ff32); +pub const SYM_osfQuickPaste: KeySym = KeySym(0x1004ff33); +pub const SYM_osfPageLeft: KeySym = KeySym(0x1004ff40); +pub const SYM_osfPageUp: KeySym = KeySym(0x1004ff41); +pub const SYM_osfPageDown: KeySym = KeySym(0x1004ff42); +pub const SYM_osfPageRight: KeySym = KeySym(0x1004ff43); +pub const SYM_osfActivate: KeySym = KeySym(0x1004ff44); +pub const SYM_osfMenuBar: KeySym = KeySym(0x1004ff45); +pub const SYM_osfLeft: KeySym = KeySym(0x1004ff51); +pub const SYM_osfUp: KeySym = KeySym(0x1004ff52); +pub const SYM_osfRight: KeySym = KeySym(0x1004ff53); +pub const SYM_osfDown: KeySym = KeySym(0x1004ff54); +pub const SYM_osfEndLine: KeySym = KeySym(0x1004ff57); +pub const SYM_osfBeginLine: KeySym = KeySym(0x1004ff58); +pub const SYM_osfEndData: KeySym = KeySym(0x1004ff59); +pub const SYM_osfBeginData: KeySym = KeySym(0x1004ff5a); +pub const SYM_osfPrevMenu: KeySym = KeySym(0x1004ff5b); +pub const SYM_osfNextMenu: KeySym = KeySym(0x1004ff5c); +pub const SYM_osfPrevField: KeySym = KeySym(0x1004ff5d); +pub const SYM_osfNextField: KeySym = KeySym(0x1004ff5e); +pub const SYM_osfSelect: KeySym = KeySym(0x1004ff60); +pub const SYM_osfInsert: KeySym = KeySym(0x1004ff63); +pub const SYM_osfUndo: KeySym = KeySym(0x1004ff65); +pub const SYM_osfMenu: KeySym = KeySym(0x1004ff67); +pub const SYM_osfCancel: KeySym = KeySym(0x1004ff69); +pub const SYM_osfHelp: KeySym = KeySym(0x1004ff6a); +pub const SYM_osfSelectAll: KeySym = KeySym(0x1004ff71); +pub const SYM_osfDeselectAll: KeySym = KeySym(0x1004ff72); +pub const SYM_osfReselect: KeySym = KeySym(0x1004ff73); +pub const SYM_osfExtend: KeySym = KeySym(0x1004ff74); +pub const SYM_osfRestore: KeySym = KeySym(0x1004ff78); +pub const SYM_osfDelete: KeySym = KeySym(0x1004ffff); +pub const SYM_Reset: KeySym = KeySym(0x1000ff6c); +pub const SYM_System: KeySym = KeySym(0x1000ff6d); +pub const SYM_User: KeySym = KeySym(0x1000ff6e); +pub const SYM_ClearLine: KeySym = KeySym(0x1000ff6f); +pub const SYM_InsertLine: KeySym = KeySym(0x1000ff70); +pub const SYM_DeleteLine: KeySym = KeySym(0x1000ff71); +pub const SYM_InsertChar: KeySym = KeySym(0x1000ff72); +pub const SYM_DeleteChar: KeySym = KeySym(0x1000ff73); +pub const SYM_BackTab: KeySym = KeySym(0x1000ff74); +pub const SYM_KP_BackTab: KeySym = KeySym(0x1000ff75); +pub const SYM_Ext16bit_L: KeySym = KeySym(0x1000ff76); +pub const SYM_Ext16bit_R: KeySym = KeySym(0x1000ff77); pub const SYM_mute_acute: KeySym = KeySym(0x100000a8); pub const SYM_mute_grave: KeySym = KeySym(0x100000a9); pub const SYM_mute_asciicircum: KeySym = KeySym(0x100000aa); diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 5d618f01..5881821c 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -12,23 +12,19 @@ //! config!(configure); //! ``` //! -//! This configuration will not allow you to interact with the compositor at all nor exit it. +//! This configuration will not allow you to exit the compositor. //! To add at least that much functionality, add the following code to `configure`: //! //! ```rust //! use jay_config::{config, quit}; -//! use jay_config::input::{get_seat, input_devices, on_new_input_device}; +//! use jay_config::input::{get_default_seat, input_devices, on_new_input_device}; //! use jay_config::keyboard::mods::ALT; //! use jay_config::keyboard::syms::SYM_q; //! //! fn configure() { -//! // Create a seat. -//! let seat = get_seat("default"); +//! let seat = get_default_seat(); //! // Create a key binding to exit the compositor. //! seat.bind(ALT | SYM_q, || quit()); -//! // Assign all current and future input devices to this seat. -//! input_devices().into_iter().for_each(move |d| d.set_seat(seat)); -//! on_new_input_device(move |d| d.set_seat(seat)); //! } //! //! config!(configure); @@ -46,7 +42,10 @@ use { crate::keyboard::ModifiedKeySym, serde::{Deserialize, Serialize}, - std::fmt::{Debug, Display, Formatter}, + std::{ + fmt::{Debug, Display, Formatter}, + time::Duration, + }, }; #[macro_use] @@ -199,3 +198,20 @@ pub fn on_idle(f: F) { pub fn on_devices_enumerated(f: F) { get!().on_devices_enumerated(f) } + +/// Returns the Jay config directory. +pub fn config_dir() -> String { + get!().config_dir() +} + +/// Returns all visible workspaces. +pub fn workspaces() -> Vec { + get!().workspaces() +} + +/// Configures the idle timeout. +/// +/// `None` disables the timeout. +pub fn set_idle(timeout: Option) { + get!().set_idle(timeout.unwrap_or_default()) +} diff --git a/jay-config/src/logging.rs b/jay-config/src/logging.rs index 4acdcfeb..1f37aa46 100644 --- a/jay-config/src/logging.rs +++ b/jay-config/src/logging.rs @@ -14,3 +14,8 @@ pub enum LogLevel { Debug, Trace, } + +/// Sets the log level of the compositor. +pub fn set_log_level(level: LogLevel) { + get!().set_log_level(level); +} diff --git a/jay-config/src/theme.rs b/jay-config/src/theme.rs index d953344f..3cbc2331 100644 --- a/jay-config/src/theme.rs +++ b/jay-config/src/theme.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; /// /// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight. // values are stored premultiplied -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct Color { r: f32, g: f32, @@ -32,13 +32,13 @@ fn to_u8(c: f32) -> u8 { } fn validate_f32(f: f32) -> bool { - f.is_normal() && f >= 0.0 && f <= 1.0 + f >= 0.0 && f <= 1.0 } fn validate_f32_all(f: [f32; 4]) -> bool { if !f.into_iter().all(validate_f32) { log::warn!( - "f32 values {:?} are not in the valid color range. Using solid black instead", + "f32 values {:?} are not in the valid color range. Using solid black instead xyz", f ); return false; @@ -72,7 +72,7 @@ impl Color { /// Creates a new color from premultiplied `f32` RGBA values. pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { - if validate_f32_all([r, g, b, a]) { + if !validate_f32_all([r, g, b, a]) { Self::BLACK } else if r > a || g > a || b > a { log::warn!("f32 values {:?} are not valid valid for a premultiplied color. Using solid black instead.", [r, g, b, a]); @@ -84,7 +84,7 @@ impl Color { /// Creates a new color from straight `f32` RGBA values. pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self { - if validate_f32_all([r, g, b, a]) { + if !validate_f32_all([r, g, b, a]) { Self::BLACK } else { Self { diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index c01fa2bf..acbd2601 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -178,6 +178,14 @@ impl Connector { self.mode().refresh_millihz } + /// Retrieves the position of the output in the global compositor space. + pub fn position(self) -> (i32, i32) { + if !self.connected() { + return (0, 0); + } + get!().connector_get_position(self) + } + /// Sets the position of the connector in the global compositor space. /// /// `x` and `y` must be non-negative and must not exceed a currently unspecified limit. @@ -212,6 +220,34 @@ impl Connector { } get!().connector_set_transform(self, transform); } + + pub fn name(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_name(self) + } + + pub fn model(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_model(self) + } + + pub fn manufacturer(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_manufacturer(self) + } + + pub fn serial_number(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_serial_number(self) + } } /// Returns all available DRM devices. @@ -247,6 +283,10 @@ pub fn on_graphics_initialized(f: F) { get!().on_graphics_initialized(f) } +pub fn connectors() -> Vec { + get!().connectors(None) +} + /// Returns the connector with the given id. /// /// The linux kernel identifies connectors by a (type, idx) tuple, e.g., `DP-0`. @@ -381,7 +421,14 @@ pub struct DrmDevice(pub u64); impl DrmDevice { /// Returns the connectors of this device. pub fn connectors(self) -> Vec { - get!().device_connectors(self) + get!().connectors(Some(self)) + } + + /// Returns the devnode of this device. + /// + /// E.g. `/dev/dri/card0`. + pub fn devnode(self) -> String { + get!().drm_device_devnode(self) } /// Returns the syspath of this device. diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 17290f12..d04f696f 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -602,10 +602,10 @@ impl MetalConnector { &self.state, Some(output.global.pos.get()), Some(rr), - output.global.preferred_scale.get(), + output.global.persistent.scale.get(), render_hw_cursor, output.has_fullscreen(), - output.global.transform.get(), + output.global.persistent.transform.get(), ); let try_direct_scanout = try_direct_scanout && self.direct_scanout_enabled() diff --git a/src/cli/randr.rs b/src/cli/randr.rs index 01509ecf..f459d29d 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -310,7 +310,7 @@ impl Randr { tc.send(jay_randr::SetScale { self_id: randr, output: &args.output, - scale: scale.0, + scale: scale.to_wl(), }); } OutputCommand::Mode(t) => { @@ -557,7 +557,7 @@ impl Randr { let mut data = data.borrow_mut(); let c = data.connectors.last_mut().unwrap(); c.output = Some(Output { - scale: Scale(msg.scale).to_f64(), + scale: Scale::from_wl(msg.scale).to_f64(), width: msg.width, height: msg.height, x: msg.x, diff --git a/src/compositor.rs b/src/compositor.rs index dc3681a4..8f48948c 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -16,7 +16,10 @@ use { dbus::Dbus, forker, globals::Globals, - ifs::{wl_output::WlOutputGlobal, wl_surface::NoneSurfaceExt}, + ifs::{ + wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, + wl_surface::NoneSurfaceExt, + }, io_uring::{IoUring, IoUringError}, leaks, logger::Logger, @@ -39,7 +42,7 @@ use { }, ahash::AHashSet, forker::ForkerProxy, - jay_config::video::GfxApi, + jay_config::{_private::DEFAULT_SEAT_NAME, video::GfxApi}, std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}, thiserror::Error, uapi::c, @@ -204,9 +207,10 @@ fn start_compositor2( dma_buf_ids: Default::default(), drm_feedback_ids: Default::default(), direct_scanout_enabled: Cell::new(true), - output_transforms: Default::default(), + persistent_output_states: Default::default(), double_click_interval_usec: Cell::new(400 * 1000), double_click_distance: Cell::new(5), + create_default_seat: Cell::new(true), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -254,6 +258,10 @@ async fn start_compositor3(state: Rc, test_future: Option) { config.configure(false); state.config.set(Some(Rc::new(config))); + if state.create_default_seat.get() && state.globals.seats.is_empty() { + state.create_seat(DEFAULT_SEAT_NAME); + } + let _geh = start_global_event_handlers(&state, &backend); state.start_xwayland(); @@ -359,6 +367,17 @@ fn init_fd_limit() { } fn create_dummy_output(state: &Rc) { + let output_id = Rc::new(OutputId { + connector: "jay-dummy-connector".to_string(), + manufacturer: "jay".to_string(), + model: "jay-dummy-output".to_string(), + serial_number: "".to_string(), + }); + let persistent_state = Rc::new(PersistentOutputState { + transform: Default::default(), + scale: Default::default(), + pos: Default::default(), + }); let dummy_output = Rc::new(OutputNode { id: state.node_ids.next(), global: Rc::new(WlOutputGlobal::new( @@ -374,18 +393,16 @@ fn create_dummy_output(state: &Rc) { drm_dev: None, async_event: Default::default(), }), - 0, Vec::new(), &backend::Mode { width: 0, height: 0, refresh_rate_millihz: 0, }, - "jay", - "dummy-output", - "0", 0, 0, + &output_id, + &persistent_state, )), jay_outputs: Default::default(), workspaces: Default::default(), diff --git a/src/config.rs b/src/config.rs index 36a1e3f7..3c3f2a43 100644 --- a/src/config.rs +++ b/src/config.rs @@ -150,7 +150,7 @@ unsafe extern "C" fn default_client_init( size: usize, ) -> *const u8 { extern "C" fn configure() { - default_config::configure(); + jay_toml_config::configure(); } jay_config::_private::client::init(srv_data, srv_unref, srv_handler, msg, size, configure) } diff --git a/src/config/handler.rs b/src/config/handler.rs index b898162d..fe76c274 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -164,9 +164,7 @@ impl ConfigProxyHandler { return; } } - let global_name = self.state.globals.name(); - let seat = WlSeatGlobal::new(global_name, name, &self.state); - self.state.globals.add_global(&self.state, &seat); + let seat = self.state.create_seat(name); self.respond(Response::GetSeat { seat: Seat(seat.id().raw() as _), }); @@ -185,13 +183,26 @@ impl ConfigProxyHandler { res } - fn handle_get_drm_device_connectors(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let mut connectors = vec![]; - for c in dev.connectors.lock().values() { - connectors.push(Connector(c.connector.id().raw() as _)); + fn handle_get_connectors( + &self, + dev: Option, + connected_only: bool, + ) -> Result<(), CphError> { + let datas: Vec<_>; + if let Some(dev) = dev { + let dev = self.get_drm_device(dev)?; + datas = dev.connectors.lock().values().cloned().collect(); + } else { + datas = self.state.connectors.lock().values().cloned().collect(); } - self.respond(Response::GetDeviceConnectors { connectors }); + let connectors = datas + .iter() + .flat_map(|d| match (connected_only, d.connected.get()) { + (false, _) | (true, true) => Some(Connector(d.connector.id().raw() as _)), + _ => None, + }) + .collect(); + self.respond(Response::GetConnectors { connectors }); Ok(()) } @@ -202,6 +213,13 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_get_drm_device_devnode(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let devnode = dev.devnode.clone().unwrap_or_default(); + self.respond(Response::GetDrmDeviceDevnode { devnode }); + Ok(()) + } + fn handle_get_drm_device_vendor(&self, dev: DrmDevice) -> Result<(), CphError> { let dev = self.get_drm_device(dev)?; let vendor = dev.vendor.clone().unwrap_or_default(); @@ -306,6 +324,35 @@ impl ConfigProxyHandler { } } + fn handle_unset_env(&self, key: &str) { + if let Some(f) = self.state.forker.get() { + f.unsetenv(key.as_bytes()); + } + } + + fn handle_get_config_dir(&self) { + let dir = self.state.config_dir.clone().unwrap_or_default(); + self.respond(Response::GetConfigDir { dir }); + } + + fn handle_get_workspaces(&self) { + let mut workspaces = vec![]; + for ws in self.state.workspaces.lock().values() { + let id = match self.workspaces_by_name.get(&ws.name) { + None => { + let id = self.workspace_ids.fetch_add(1); + let name = Rc::new(ws.name.clone()); + self.workspaces_by_name.set(name.clone(), id); + self.workspaces_by_id.set(id, name); + id + } + Some(id) => id, + }; + workspaces.push(Workspace(id)); + } + self.respond(Response::GetWorkspaces { workspaces }); + } + fn handle_program_timer( &self, timer: JayTimer, @@ -690,6 +737,26 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_get_input_device_syspath(&self, device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + self.respond(Response::GetInputDeviceSyspath { + syspath: dev.syspath.clone().unwrap_or_default(), + }); + Ok(()) + } + + fn handle_get_input_device_devnode(&self, device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + self.respond(Response::GetInputDeviceDevnode { + devnode: dev.devnode.clone().unwrap_or_default(), + }); + Ok(()) + } + + fn handle_set_idle(&self, timeout: Duration) { + self.state.idle.set_timeout(timeout); + } + fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_connector(connector)?; self.respond(Response::ConnectorConnected { @@ -749,6 +816,38 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + self.respond(Response::GetConnectorName { + name: connector.name.clone(), + }); + Ok(()) + } + + fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorModel { + model: connector.monitor_info.product.clone(), + }); + Ok(()) + } + + fn handle_connector_manufacturer(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorManufacturer { + manufacturer: connector.monitor_info.manufacturer.clone(), + }); + Ok(()) + } + + fn handle_connector_serial_number(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorSerialNumber { + serial_number: connector.monitor_info.serial_number.clone(), + }); + Ok(()) + } + fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> { let seat = self.get_seat(seat)?; if size < 0 { @@ -795,7 +894,7 @@ impl ConfigProxyHandler { fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_output(connector)?; self.respond(Response::ConnectorGetScale { - scale: connector.node.global.preferred_scale.get().to_f64(), + scale: connector.node.global.persistent.scale.get().to_f64(), }); Ok(()) } @@ -850,6 +949,13 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_get_position(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + let (x, y) = connector.node.global.pos.get().position(); + self.respond(Response::ConnectorGetPosition { x, y }); + Ok(()) + } + fn handle_connector_set_enabled( &self, connector: Connector, @@ -1013,6 +1119,19 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_log_level(&self, level: LogLevel) { + let level = match level { + LogLevel::Error => Level::Error, + LogLevel::Warn => Level::Warn, + LogLevel::Info => Level::Info, + LogLevel::Debug => Level::Debug, + LogLevel::Trace => Level::Trace, + }; + if let Some(logger) = &self.state.logger { + logger.set_level(level); + } + } + fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> { let kb = self.get_kb(kb)?; kb.grab(grab); @@ -1252,6 +1371,10 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_destroy_keymap(&self, keymap: Keymap) { + self.keymaps.remove(&keymap); + } + pub fn handle_request(self: &Rc, msg: &[u8]) { if let Err(e) = self.handle_request_(msg) { log::error!("Could not handle client request: {}", ErrorFmt(e)); @@ -1401,7 +1524,7 @@ impl ConfigProxyHandler { } ClientMessage::Reload => self.handle_reload(), ClientMessage::GetDeviceConnectors { device } => self - .handle_get_drm_device_connectors(device) + .handle_get_connectors(Some(device), false) .wrn("get_device_connectors")?, ClientMessage::GetDrmDeviceSyspath { device } => self .handle_get_drm_device_syspath(device) @@ -1516,6 +1639,43 @@ impl ConfigProxyHandler { env, fds, } => self.handle_run(prog, args, env, fds).wrn("run")?, + ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false), + ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap), + ClientMessage::GetConnectorName { connector } => self + .handle_connector_name(connector) + .wrn("connector_name")?, + ClientMessage::GetConnectorModel { connector } => self + .handle_connector_model(connector) + .wrn("connector_model")?, + ClientMessage::GetConnectorManufacturer { connector } => self + .handle_connector_manufacturer(connector) + .wrn("connector_manufacturer")?, + ClientMessage::GetConnectorSerialNumber { connector } => self + .handle_connector_serial_number(connector) + .wrn("connector_serial_number")?, + ClientMessage::GetConnectors { + device, + connected_only, + } => self + .handle_get_connectors(device, connected_only) + .wrn("get_connectors")?, + ClientMessage::ConnectorGetPosition { connector } => self + .handle_connector_get_position(connector) + .wrn("connector_get_position")?, + ClientMessage::GetConfigDir => self.handle_get_config_dir(), + ClientMessage::GetWorkspaces => self.handle_get_workspaces(), + ClientMessage::UnsetEnv { key } => self.handle_unset_env(key), + ClientMessage::SetLogLevel { level } => self.handle_set_log_level(level), + ClientMessage::GetDrmDeviceDevnode { device } => self + .handle_get_drm_device_devnode(device) + .wrn("get_drm_device_devnode")?, + ClientMessage::GetInputDeviceSyspath { device } => self + .handle_get_input_device_syspath(device) + .wrn("get_input_device_syspath")?, + ClientMessage::GetInputDeviceDevnode { device } => self + .handle_get_input_device_devnode(device) + .wrn("get_input_device_devnode")?, + ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout), } Ok(()) } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 8d2716c7..a9f5d280 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -316,7 +316,7 @@ impl dyn GfxFramebuffer { scale, render_hardware_cursor, node.has_fullscreen(), - node.global.transform.get(), + node.global.persistent.transform.get(), ) } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 560b301a..765a25ef 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -204,8 +204,8 @@ impl JayInput { .map(|s| s.seat_name()) .unwrap_or_default(), id: data.id.raw(), - syspath: data.syspath.as_deref().unwrap_or_default(), - devnode: data.devnode.as_deref().unwrap_or_default(), + syspath: data.data.syspath.as_deref().unwrap_or_default(), + devnode: data.data.devnode.as_deref().unwrap_or_default(), name: dev.name().as_str(), capabilities: &caps, accel_available: accel_profile.is_some() as _, diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 24cb21a5..735f65a8 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -92,12 +92,12 @@ impl JayRandr { let pos = global.pos.get(); self.client.event(Output { self_id: self.id, - scale: global.preferred_scale.get().0, + scale: global.persistent.scale.get().to_wl(), width: pos.width(), height: pos.height(), x: pos.x1(), y: pos.y1(), - transform: global.transform.get().to_wl(), + transform: global.persistent.transform.get().to_wl(), manufacturer: &output.monitor_info.manufacturer, product: &output.monitor_info.product, serial_number: &output.monitor_info.serial_number, @@ -217,7 +217,7 @@ impl JayRandr { let Some(c) = self.get_output(req.output) else { return Ok(()); }; - c.node.set_preferred_scale(Scale(req.scale)); + c.node.set_preferred_scale(Scale::from_wl(req.scale)); Ok(()) } diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 77541294..f3a7a791 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -181,7 +181,7 @@ impl JayScreencast { x_off, y_off, size, - on.global.transform.get(), + on.global.persistent.transform.get(), ); self.client.event(Ready { self_id: self.id, diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index d93f3b50..8fb3f1cc 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -13,7 +13,7 @@ use { rect::Rect, state::{ConnectorData, State}, time::Time, - tree::OutputNode, + tree::{calculate_logical_size, OutputNode}, utils::{ buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, @@ -75,12 +75,18 @@ pub struct WlOutputGlobal { pub pending_captures: LinkedList>, pub destroyed: Cell, pub legacy_scale: Cell, - pub preferred_scale: Cell, + pub persistent: Rc, +} + +pub struct PersistentOutputState { pub transform: Cell, + pub scale: Cell, + pub pos: Cell<(i32, i32)>, } #[derive(Eq, PartialEq, Hash)] pub struct OutputId { + pub connector: String, pub manufacturer: String, pub model: String, pub serial_number: String, @@ -96,33 +102,26 @@ impl WlOutputGlobal { name: GlobalName, state: &Rc, connector: &Rc, - x1: i32, modes: Vec, mode: &backend::Mode, - manufacturer: &str, - product: &str, - serial_number: &str, width_mm: i32, height_mm: i32, + output_id: &Rc, + persistent_state: &Rc, ) -> Self { - let output_id = Rc::new(OutputId { - manufacturer: manufacturer.to_string(), - model: product.to_string(), - serial_number: serial_number.to_string(), - }); - let transform = state - .output_transforms - .borrow() - .get(&output_id) - .copied() - .unwrap_or(Transform::None); - let (width, height) = transform.maybe_swap((mode.width, mode.height)); + let (x, y) = persistent_state.pos.get(); + let scale = persistent_state.scale.get(); + let (width, height) = calculate_logical_size( + (mode.width, mode.height), + persistent_state.transform.get(), + scale, + ); Self { name, state: state.clone(), connector: connector.clone(), - pos: Cell::new(Rect::new_sized(x1, 0, width, height).unwrap()), - output_id, + pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()), + output_id: output_id.clone(), mode: Cell::new(*mode), modes, node: Default::default(), @@ -132,9 +131,8 @@ impl WlOutputGlobal { unused_captures: Default::default(), pending_captures: Default::default(), destroyed: Cell::new(false), - legacy_scale: Cell::new(1), - preferred_scale: Cell::new(crate::scale::Scale::from_int(1)), - transform: Cell::new(transform), + legacy_scale: Cell::new(scale.round_up()), + persistent: persistent_state.clone(), } } @@ -289,7 +287,10 @@ impl WlOutputGlobal { pub fn pixel_size(&self) -> (i32, i32) { let mode = self.mode.get(); - self.transform.get().maybe_swap((mode.width, mode.height)) + self.persistent + .transform + .get() + .maybe_swap((mode.width, mode.height)) } } @@ -336,7 +337,7 @@ impl WlOutput { subpixel: SP_UNKNOWN, make: &self.global.output_id.manufacturer, model: &self.global.output_id.model, - transform: self.global.transform.get().to_wl(), + transform: self.global.persistent.transform.get().to_wl(), }; self.client.event(event); } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index d027602e..3edf241b 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -272,9 +272,9 @@ impl WlSeatGlobal { let (x, y) = self.get_position(); for output in self.state.root.outputs.lock().values() { if let Some(hc) = output.hardware_cursor.get() { - let transform = output.global.transform.get(); + let transform = output.global.persistent.transform.get(); let render = render | output.hardware_cursor_needs_render.take(); - let scale = output.global.preferred_scale.get(); + let scale = output.global.persistent.scale.get(); let extents = cursor.extents_at_scale(scale); let (hc_width, hc_height) = hc.size(); if render { diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 35f69d5b..ca4079cf 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -357,10 +357,10 @@ impl WlSurface { } output.global.send_enter(self); old.global.send_leave(self); - if old.global.preferred_scale.get() != output.global.preferred_scale.get() { + if old.global.persistent.scale.get() != output.global.persistent.scale.get() { self.on_scale_change(); } - if old.global.transform.get() != output.global.transform.get() { + if old.global.persistent.transform.get() != output.global.persistent.transform.get() { self.send_preferred_buffer_transform(); } let children = self.children.borrow_mut(); @@ -459,7 +459,7 @@ impl WlSurface { if self.version >= TRANSFORM_SINCE { self.client.event(PreferredBufferTransform { self_id: self.id, - transform: self.output.get().global.transform.get().to_wl() as _, + transform: self.output.get().global.persistent.transform.get().to_wl() as _, }); } } diff --git a/src/ifs/wl_surface/wp_fractional_scale_v1.rs b/src/ifs/wl_surface/wp_fractional_scale_v1.rs index cf948420..6f86fdcc 100644 --- a/src/ifs/wl_surface/wp_fractional_scale_v1.rs +++ b/src/ifs/wl_surface/wp_fractional_scale_v1.rs @@ -39,7 +39,15 @@ impl WpFractionalScaleV1 { pub fn send_preferred_scale(&self) { self.client.event(PreferredScale { self_id: self.id, - scale: self.surface.output.get().global.preferred_scale.get().0, + scale: self + .surface + .output + .get() + .global + .persistent + .scale + .get() + .to_wl(), }); } diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index 3e653c28..63704fa3 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -100,7 +100,7 @@ impl ZwlrScreencopyManagerV1 { let mode = output.global.mode.get(); let mut rect = Rect::new_sized(0, 0, mode.width, mode.height).unwrap(); if let Some(region) = region { - let scale = output.global.preferred_scale.get().to_f64(); + let scale = output.global.persistent.scale.get().to_f64(); let x1 = (region.x1() as f64 * scale).round() as i32; let y1 = (region.y1() as f64 * scale).round() as i32; let x2 = (region.x2() as f64 * scale).round() as i32; diff --git a/src/it/tests/t0005_create_seat.rs b/src/it/tests/t0005_create_seat.rs index 90d1e23b..4757d76a 100644 --- a/src/it/tests/t0005_create_seat.rs +++ b/src/it/tests/t0005_create_seat.rs @@ -12,13 +12,13 @@ testcase!(); async fn test(run: Rc) -> Result<(), TestError> { let client = run.create_client().await?; - tassert!(client.registry.seats.is_empty()); + tassert_eq!(client.registry.seats.len(), 1); - let seat = run.get_seat("default")?; + let seat = run.get_seat("new-seat")?; client.sync().await; - tassert_eq!(client.registry.seats.len(), 1); + tassert_eq!(client.registry.seats.len(), 2); let client_seat = client.registry.seats.get(&seat.name()); tassert!(client_seat.is_some()); diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 668675f6..5fbb3793 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -852,7 +852,7 @@ impl UsrWlBufferOwner for GuiBuffer { impl UsrWpFractionalScaleOwner for WindowData { fn preferred_scale(self: Rc, ev: &PreferredScale) { let mut layout = self.first_scale.replace(false); - let scale = Scale(ev.scale); + let scale = Scale::from_wl(ev.scale); layout |= self.scale.replace(scale) != scale; if layout { self.layout(); diff --git a/src/renderer.rs b/src/renderer.rs index 5231c8b9..e9999c21 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -148,7 +148,7 @@ impl Renderer<'_> { let c = theme.colors.attention_requested_background.get(); self.base .fill_boxes2(&rd.attention_requested_workspaces, &c, x, y); - let scale = output.global.preferred_scale.get(); + let scale = output.global.persistent.scale.get(); for title in &rd.titles { let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); self.base diff --git a/src/scale.rs b/src/scale.rs index 96900ca8..fb16ba37 100644 --- a/src/scale.rs +++ b/src/scale.rs @@ -5,7 +5,13 @@ const BASEF: f64 = BASE as f64; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] -pub struct Scale(pub u32); +pub struct Scale(u32); + +impl Default for Scale { + fn default() -> Self { + Scale::from_int(1) + } +} impl Scale { pub fn from_int(f: u32) -> Self { @@ -23,6 +29,14 @@ impl Scale { pub fn round_up(self) -> u32 { self.0.saturating_add(BASE - 1) / BASE } + + pub fn from_wl(wl: u32) -> Self { + Self(wl) + } + + pub fn to_wl(self) -> u32 { + self.0 + } } impl PartialEq for Scale { diff --git a/src/state.rs b/src/state.rs index 31062e8b..66942fe2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -27,7 +27,7 @@ use { jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_drm::WlDrmGlobal, - wl_output::OutputId, + wl_output::{OutputId, PersistentOutputState}, wl_seat::{SeatIds, WlSeatGlobal}, wl_surface::{ zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, @@ -153,9 +153,10 @@ pub struct State { pub dma_buf_ids: DmaBufIds, pub drm_feedback_ids: DrmFeedbackIds, pub direct_scanout_enabled: Cell, - pub output_transforms: RefCell, Transform>>, + pub persistent_output_states: CopyHashMap, Rc>, pub double_click_interval_usec: Cell, pub double_click_distance: Cell, + pub create_default_seat: Cell, } // impl Drop for State { @@ -215,14 +216,14 @@ pub struct InputDeviceData { pub id: InputDeviceId, pub data: Rc, pub async_event: Rc, - pub syspath: Option, - pub devnode: Option, } pub struct DeviceHandlerData { pub seat: CloneCell>>, pub px_per_scroll_wheel: Cell, pub device: Rc, + pub syspath: Option, + pub devnode: Option, } pub struct ConnectorData { @@ -771,7 +772,7 @@ impl State { self, Some(output.global.pos.get()), Some(rr), - output.global.preferred_scale.get(), + output.global.persistent.scale.get(), render_hw_cursor, ); output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None); @@ -924,4 +925,11 @@ impl State { capture.send_failed(); } } + + pub fn create_seat(self: &Rc, name: &str) -> Rc { + let global_name = self.globals.name(); + let seat = WlSeatGlobal::new(global_name, name, self); + self.globals.add_global(self, &seat); + seat + } } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 5c1df848..6d78b454 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -1,7 +1,7 @@ use { crate::{ backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}, - ifs::wl_output::WlOutputGlobal, + ifs::wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, state::{ConnectorData, OutputData, State}, tree::{OutputNode, OutputRenderData}, utils::{asyncevent::AsyncEvent, clonecell::CloneCell}, @@ -80,27 +80,45 @@ impl ConnectorHandler { log::info!("Connector {} connected", self.data.connector.kernel_id()); self.data.connected.set(true); let name = self.state.globals.name(); - let x1 = self - .state - .root - .outputs - .lock() - .values() - .map(|o| o.global.pos.get().x2()) - .max() - .unwrap_or(0); + let output_id = Rc::new(OutputId { + connector: self.data.name.clone(), + manufacturer: info.manufacturer.clone(), + model: info.product.clone(), + serial_number: info.serial_number.clone(), + }); + let desired_state = match self.state.persistent_output_states.get(&output_id) { + Some(ds) => ds, + _ => { + let x1 = self + .state + .root + .outputs + .lock() + .values() + .map(|o| o.global.pos.get().x2()) + .max() + .unwrap_or(0); + let ds = Rc::new(PersistentOutputState { + transform: Default::default(), + scale: Default::default(), + pos: Cell::new((x1, 0)), + }); + self.state + .persistent_output_states + .set(output_id.clone(), ds.clone()); + ds + } + }; let global = Rc::new(WlOutputGlobal::new( name, &self.state, &self.data, - x1, info.modes.clone(), &info.initial_mode, - &info.manufacturer, - &info.product, - &info.serial_number, info.width_mm, info.height_mm, + &output_id, + &desired_state, )); let on = Rc::new(OutputNode { id: self.state.node_ids.next(), @@ -130,8 +148,8 @@ impl ConnectorHandler { update_render_data_scheduled: Cell::new(false), hardware_cursor_needs_render: Cell::new(false), }); - self.state.add_output_scale(on.global.preferred_scale.get()); - let mode = info.initial_mode; + self.state + .add_output_scale(on.global.persistent.scale.get()); let output_data = Rc::new(OutputData { connector: self.data.clone(), monitor_info: info, @@ -140,8 +158,11 @@ impl ConnectorHandler { self.state.outputs.set(self.id, output_data); if self.state.outputs.len() == 1 { let seats = self.state.globals.seats.lock(); + let pos = global.pos.get(); + let x = (pos.x1() + pos.x2()) / 2; + let y = (pos.y1() + pos.y2()) / 2; for seat in seats.values() { - seat.set_position(x1 + mode.width / 2, mode.height / 2); + seat.set_position(x, y); } } global.node.set(Some(on.clone())); @@ -277,7 +298,7 @@ impl ConnectorHandler { dev.connectors.remove(&self.id); } self.state - .remove_output_scale(on.global.preferred_scale.get()); + .remove_output_scale(on.global.persistent.scale.get()); let _ = self.state.remove_global(&*global); } } diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 2d7601d5..b2c84210 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -6,14 +6,21 @@ use { tasks::udev_utils::{udev_props, UdevProps}, utils::asyncevent::AsyncEvent, }, + jay_config::_private::DEFAULT_SEAT_NAME, std::{cell::Cell, rc::Rc}, }; pub fn handle(state: &Rc, dev: Rc) { + let props = match dev.dev_t() { + None => UdevProps::default(), + Some(dev_t) => udev_props(dev_t, 3), + }; let data = Rc::new(DeviceHandlerData { seat: Default::default(), px_per_scroll_wheel: Cell::new(PX_PER_SCROLL), device: dev.clone(), + syspath: props.syspath, + devnode: props.devnode, }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { @@ -23,10 +30,6 @@ pub fn handle(state: &Rc, dev: Rc) { ae: ae.clone(), }; let handler = state.eng.spawn(oh.handle()); - let props = match dev.dev_t() { - None => UdevProps::default(), - Some(dev_t) => udev_props(dev_t, 3), - }; state.input_device_handlers.borrow_mut().insert( dev.id(), InputDeviceData { @@ -34,8 +37,6 @@ pub fn handle(state: &Rc, dev: Rc) { id: dev.id(), data, async_event: ae, - syspath: props.syspath, - devnode: props.devnode, }, ); } @@ -53,6 +54,12 @@ impl DeviceHandler { let ae = self.ae.clone(); self.dev.on_change(Rc::new(move || ae.trigger())); } + for seat in self.state.globals.seats.lock().values() { + if seat.seat_name() == DEFAULT_SEAT_NAME { + self.data.seat.set(Some(seat.clone())); + break; + } + } if let Some(config) = self.state.config.get() { config.new_input_device(self.dev.id()); } diff --git a/src/tree/output.rs b/src/tree/output.rs index c81a0c7b..b1f0c7d8 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -30,7 +30,7 @@ use { }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - linkedlist::LinkedList, scroller::Scroller, + linkedlist::LinkedList, scroller::Scroller, transform_ext::TransformExt, }, wire::{JayOutputId, JayScreencastId}, }, @@ -120,7 +120,7 @@ impl OutputNode { } pub fn set_preferred_scale(self: &Rc, scale: Scale) { - let old_scale = self.global.preferred_scale.replace(scale); + let old_scale = self.global.persistent.scale.replace(scale); if scale == old_scale { return; } @@ -161,7 +161,7 @@ impl OutputNode { let font = self.state.theme.font.borrow_mut(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); - let scale = self.global.preferred_scale.get(); + let scale = self.global.persistent.scale.get(); let scale = if scale != 1 { Some(scale.to_f64()) } else { @@ -388,7 +388,7 @@ impl OutputNode { } pub fn update_mode(self: &Rc, mode: Mode) { - self.update_mode_and_transform(mode, self.global.transform.get()); + self.update_mode_and_transform(mode, self.global.persistent.transform.get()); } pub fn update_transform(self: &Rc, transform: Transform) { @@ -397,17 +397,13 @@ impl OutputNode { pub fn update_mode_and_transform(self: &Rc, mode: Mode, transform: Transform) { let old_mode = self.global.mode.get(); - let old_transform = self.global.transform.get(); + let old_transform = self.global.persistent.transform.get(); if (old_mode, old_transform) == (mode, transform) { return; } let (old_width, old_height) = self.global.pixel_size(); self.global.mode.set(mode); - self.state - .output_transforms - .borrow_mut() - .insert(self.global.output_id.clone(), transform); - self.global.transform.set(transform); + self.global.persistent.transform.set(transform); let (new_width, new_height) = self.global.pixel_size(); self.change_extents_(&self.calculate_extents()); @@ -436,18 +432,18 @@ impl OutputNode { } fn calculate_extents(&self) -> Rect { - let (mut width, mut height) = self.global.pixel_size(); - let scale = self.global.preferred_scale.get(); - if scale != 1 { - let scale = scale.to_f64(); - width = (width as f64 / scale).round() as _; - height = (height as f64 / scale).round() as _; - } + let mode = self.global.mode.get(); + let (width, height) = calculate_logical_size( + (mode.width, mode.height), + self.global.persistent.transform.get(), + self.global.persistent.scale.get(), + ); let pos = self.global.pos.get(); pos.with_size(width, height).unwrap() } fn change_extents_(self: &Rc, rect: &Rect) { + self.global.persistent.pos.set((rect.x1(), rect.y1())); self.global.pos.set(*rect); self.state.root.update_extents(); self.schedule_update_render_data(); @@ -766,3 +762,17 @@ impl Node for OutputNode { true } } + +pub fn calculate_logical_size( + mode: (i32, i32), + transform: Transform, + scale: crate::scale::Scale, +) -> (i32, i32) { + let (mut width, mut height) = transform.maybe_swap(mode); + if scale != 1 { + let scale = scale.to_f64(); + width = (width as f64 / scale).round() as _; + height = (height as f64 / scale).round() as _; + } + (width, height) +} diff --git a/toml-config/Cargo.toml b/toml-config/Cargo.toml new file mode 100644 index 00000000..a6d0c4e2 --- /dev/null +++ b/toml-config/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "jay-toml-config" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +jay-config = { path = "../jay-config" } +log = "0.4.14" +thiserror = "1.0.57" +error_reporter = "1.0.0" +phf = { version = "0.11.2", features = ["macros"] } +indexmap = "2.2.5" +bstr = { version = "1.9.1", default-features = false } +ahash = "0.8.11" + +[dev-dependencies] +simplelog = { version = "0.12.2", features = ["test"] } +serde_json = "1.0.114" +walkdir = "2.5.0" diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs new file mode 100644 index 00000000..8fb5023b --- /dev/null +++ b/toml-config/src/config.rs @@ -0,0 +1,307 @@ +mod context; +pub mod error; +mod extractor; +mod keysyms; +mod parser; +mod parsers; +mod spanned; +mod value; + +use { + crate::{ + config::{ + context::Context, + parsers::config::{ConfigParser, ConfigParserError}, + }, + toml::{self}, + }, + jay_config::{ + input::acceleration::AccelProfile, + keyboard::{Keymap, ModifiedKeySym}, + logging::LogLevel, + status::MessageFormat, + theme::Color, + video::{GfxApi, Transform}, + Axis, Direction, + }, + std::{ + error::Error, + fmt::{Display, Formatter}, + time::Duration, + }, + thiserror::Error, + toml::toml_parser, +}; + +#[derive(Debug, Copy, Clone)] +pub enum SimpleCommand { + Close, + DisablePointerConstraint, + Focus(Direction), + FocusParent, + Move(Direction), + None, + Quit, + ReloadConfigSo, + ReloadConfigToml, + Split(Axis), + ToggleFloating, + ToggleFullscreen, + ToggleMono, + ToggleSplit, +} + +#[derive(Debug, Clone)] +pub enum Action { + ConfigureConnector { con: ConfigConnector }, + ConfigureDirectScanout { enabled: bool }, + ConfigureDrmDevice { dev: ConfigDrmDevice }, + ConfigureIdle { idle: Duration }, + ConfigureInput { input: Input }, + ConfigureOutput { out: Output }, + Exec { exec: Exec }, + Multi { actions: Vec }, + 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, + pub idle: Option, +} + +#[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..194b3ea5 --- /dev/null +++ b/toml-config/src/config/extractor.rs @@ -0,0 +1,281 @@ +use { + crate::{ + config::context::Context, + toml::{ + toml_span::{Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + ahash::AHashSet, + error_reporter::Report, + indexmap::IndexMap, + thiserror::Error, +}; + +pub struct Extractor<'v> { + cx: &'v Context<'v>, + table: &'v IndexMap, Spanned>, + 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 u64")] + U64, + #[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!(n64, u64, U64); +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..3bd9abc6 --- /dev/null +++ b/toml-config/src/config/parsers.rs @@ -0,0 +1,48 @@ +use { + crate::{ + config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + toml::toml_span::Span, + }, + thiserror::Error, +}; + +pub mod action; +mod color; +pub mod config; +mod connector; +mod connector_match; +mod drm_device; +mod drm_device_match; +mod env; +pub mod exec; +mod gfx_api; +mod idle; +mod input; +mod input_match; +pub mod keymap; +mod log_level; +mod mode; +pub mod modified_keysym; +mod output; +mod output_match; +pub mod shortcuts; +mod status; +mod theme; + +#[derive(Debug, Error)] +pub enum StringParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), +} + +pub struct StringParser; + +impl Parser for StringParser { + type Value = String; + type Error = StringParserError; + const EXPECTED: &'static [DataType] = &[DataType::String]; + + fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult { + 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..b697f82e --- /dev/null +++ b/toml-config/src/config/parsers/action.rs @@ -0,0 +1,308 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{arr, bol, n32, opt, str, val, Extractor, ExtractorError}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + parsers::{ + connector::{ConnectorParser, ConnectorParserError}, + drm_device::{DrmDeviceParser, DrmDeviceParserError}, + drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError}, + env::{EnvParser, EnvParserError}, + exec::{ExecParser, ExecParserError}, + gfx_api::{GfxApiParser, GfxApiParserError}, + idle::{IdleParser, IdleParserError}, + input::{InputParser, InputParserError}, + keymap::{KeymapParser, KeymapParserError}, + log_level::{LogLevelParser, LogLevelParserError}, + output::{OutputParser, OutputParserError}, + status::{StatusParser, StatusParserError}, + theme::{ThemeParser, ThemeParserError}, + StringParser, StringParserError, + }, + Action, + }, + toml::{ + toml_span::{Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + jay_config::Axis::{Horizontal, Vertical}, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum ActionParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + StringParser(#[from] StringParserError), + #[error("Unknown type {0}")] + UnknownType(String), + #[error("Unknown simple action {0}")] + UnknownSimpleAction(String), + #[error(transparent)] + Extract(#[from] ExtractorError), + #[error("Could not parse the exec action")] + Exec(#[from] ExecParserError), + #[error("Could not parse the configure-connector action")] + ConfigureConnector(#[from] ConnectorParserError), + #[error("Could not parse the configure-input action")] + ConfigureInput(#[from] InputParserError), + #[error("Could not parse the configure-output action")] + ConfigureOutput(#[from] OutputParserError), + #[error("Could not parse the environment variables")] + Env(#[from] EnvParserError), + #[error("Could not parse a set-keymap action")] + SetKeymap(#[from] KeymapParserError), + #[error("Could not parse a set-status action")] + Status(#[from] StatusParserError), + #[error("Could not parse a set-theme action")] + Theme(#[from] ThemeParserError), + #[error("Could not parse a set-log-level action")] + SetLogLevel(#[from] LogLevelParserError), + #[error("Could not parse a set-gfx-api action")] + GfxApi(#[from] GfxApiParserError), + #[error("Could not parse a configure-drm-device action")] + DrmDevice(#[from] DrmDeviceParserError), + #[error("Could not parse a set-render-device action")] + SetRenderDevice(#[from] DrmDeviceMatchParserError), + #[error("Could not parse a configure-idle action")] + ConfigureIdle(#[from] IdleParserError), +} + +pub struct ActionParser<'a>(pub &'a Context<'a>); + +impl ActionParser<'_> { + fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult { + use {crate::config::SimpleCommand::*, jay_config::Direction::*}; + let cmd = match string { + "focus-left" => Focus(Left), + "focus-down" => Focus(Down), + "focus-up" => Focus(Up), + "focus-right" => Focus(Right), + "move-left" => Move(Left), + "move-down" => Move(Down), + "move-up" => Move(Up), + "move-right" => Move(Right), + "split-horizontal" => Split(Horizontal), + "split-vertical" => Split(Vertical), + "toggle-split" => ToggleSplit, + "toggle-mono" => ToggleMono, + "toggle-fullscreen" => ToggleFullscreen, + "focus-parent" => FocusParent, + "close" => Close, + "disable-pointer-constraint" => DisablePointerConstraint, + "toggle-floating" => ToggleFloating, + "quit" => Quit, + "reload-config-toml" => ReloadConfigToml, + "reload-config-so" => ReloadConfigSo, + "none" => None, + _ => { + return Err(ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)) + } + }; + Ok(Action::SimpleCommand { cmd }) + } + + fn parse_multi(&mut self, _span: Span, array: &[Spanned]) -> 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_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let idle = ext + .extract(val("idle"))? + .parse_map(&mut IdleParser(self.0))?; + Ok(Action::ConfigureIdle { idle }) + } + + fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + 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), + "configure-idle" => self.parse_configure_idle(&mut ext), + v => { + ext.ignore_unused(); + return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span)); + } + }; + drop(ext); + res + } +} 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..78da91df --- /dev/null +++ b/toml-config/src/config/parsers/config.rs @@ -0,0 +1,279 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{arr, bol, opt, recover, val, Extractor, ExtractorError}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + parsers::{ + action::ActionParser, + connector::ConnectorsParser, + drm_device::DrmDevicesParser, + drm_device_match::DrmDeviceMatchParser, + env::EnvParser, + gfx_api::GfxApiParser, + idle::IdleParser, + input::InputsParser, + keymap::KeymapParser, + log_level::LogLevelParser, + output::OutputsParser, + shortcuts::{ShortcutsParser, ShortcutsParserError}, + status::StatusParser, + theme::ThemeParser, + }, + spanned::SpannedErrorExt, + Action, Config, Theme, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum ConfigParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extractor(#[from] ExtractorError), + #[error("Could not parse the shortcuts")] + ParseShortcuts(#[source] ShortcutsParserError), +} + +pub struct ConfigParser<'a>(pub &'a Context<'a>); + +impl ConfigParser<'_> { + fn parse_action(&self, name: &str, action: Option>) -> 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, + _, + idle_val, + ), + ) = ext.extract(( + ( + opt(val("keymap")), + opt(val("shortcuts")), + opt(val("on-graphics-initialized")), + opt(val("status")), + opt(val("outputs")), + opt(val("connectors")), + recover(opt(bol("workspace-capture"))), + opt(val("env")), + opt(val("on-startup")), + recover(opt(arr("keymaps"))), + ), + ( + opt(val("log-level")), + opt(val("theme")), + opt(val("gfx-api")), + opt(val("drm-devices")), + recover(opt(bol("direct-scanout"))), + opt(val("render-device")), + opt(val("inputs")), + opt(val("on-idle")), + opt(val("$schema")), + opt(val("idle")), + ), + ))?; + let mut keymap = None; + if let Some(value) = keymap_val { + match value.parse(&mut KeymapParser { + cx: self.0, + definition: false, + }) { + Ok(m) => keymap = Some(m), + Err(e) => { + log::warn!("Could not parse the keymap: {}", self.0.error(e)); + } + } + } + let mut shortcuts = vec![]; + if let Some(value) = shortcuts_val { + shortcuts = value + .parse(&mut ShortcutsParser(self.0)) + .map_spanned_err(ConfigParserError::ParseShortcuts)?; + } + if shortcuts.is_empty() { + log::warn!("Config defines no shortcuts"); + } + let on_graphics_initialized = + self.parse_action("on-graphics-initialized", on_graphics_init_val); + let on_idle = self.parse_action("on-idle", on_idle_val); + let on_startup = self.parse_action("on-startup", on_startup_val); + let mut status = None; + if let Some(value) = status_val { + match value.parse(&mut StatusParser(self.0)) { + Ok(v) => status = Some(v), + Err(e) => log::warn!("Could not parse the status config: {}", self.0.error(e)), + } + } + let mut outputs = vec![]; + if let Some(value) = outputs_val { + match value.parse(&mut OutputsParser(self.0)) { + Ok(v) => outputs = v, + Err(e) => log::warn!("Could not parse the outputs: {}", self.0.error(e)), + } + } + let mut connectors = vec![]; + if let Some(value) = connectors_val { + match value.parse(&mut ConnectorsParser(self.0)) { + Ok(v) => connectors = v, + Err(e) => log::warn!("Could not parse the connectors: {}", self.0.error(e)), + } + } + let mut env = vec![]; + if let Some(value) = env_val { + match value.parse(&mut EnvParser) { + Ok(v) => env = v, + Err(e) => log::warn!( + "Could not parse the environment variables: {}", + self.0.error(e) + ), + } + } + let mut keymaps = vec![]; + if let Some(value) = keymaps_val { + for value in value.value { + match value.parse(&mut KeymapParser { + cx: self.0, + definition: true, + }) { + Ok(m) => keymaps.push(m), + Err(e) => { + log::warn!("Could not parse a keymap: {}", self.0.error(e)); + } + } + } + } + let mut log_level = None; + if let Some(value) = log_level_val { + match value.parse(&mut LogLevelParser) { + Ok(v) => log_level = Some(v), + Err(e) => { + log::warn!("Could not parse the log level: {}", self.0.error(e)); + } + } + } + let mut theme = Theme::default(); + if let Some(value) = theme_val { + match value.parse(&mut ThemeParser(self.0)) { + Ok(v) => theme = v, + Err(e) => { + log::warn!("Could not parse the theme: {}", self.0.error(e)); + } + } + } + let mut gfx_api = None; + if let Some(value) = gfx_api_val { + match value.parse(&mut GfxApiParser) { + Ok(v) => gfx_api = Some(v), + Err(e) => { + log::warn!("Could not parse the graphics API: {}", self.0.error(e)); + } + } + } + let mut drm_devices = vec![]; + if let Some(value) = drm_devices_val { + match value.parse(&mut DrmDevicesParser(self.0)) { + Ok(v) => drm_devices = v, + Err(e) => { + log::warn!("Could not parse the drm devices: {}", self.0.error(e)); + } + } + } + let mut render_device = None; + if let Some(value) = render_device_val { + match value.parse(&mut DrmDeviceMatchParser(self.0)) { + Ok(v) => render_device = Some(v), + Err(e) => { + log::warn!("Could not parse the render device: {}", self.0.error(e)); + } + } + } + let mut inputs = vec![]; + if let Some(value) = inputs_val { + match value.parse(&mut InputsParser(self.0)) { + Ok(v) => inputs = v, + Err(e) => { + log::warn!("Could not parse the inputs: {}", self.0.error(e)); + } + } + } + let mut idle = None; + if let Some(value) = idle_val { + match value.parse(&mut IdleParser(self.0)) { + Ok(v) => idle = Some(v), + Err(e) => { + log::warn!("Could not parse the idle timeout: {}", self.0.error(e)); + } + } + } + Ok(Config { + keymap, + shortcuts, + on_graphics_initialized, + on_idle, + status, + outputs, + connectors, + workspace_capture: workspace_capture.despan().unwrap_or(true), + env, + on_startup, + keymaps, + log_level, + theme, + gfx_api, + drm_devices, + direct_scanout_enabled: direct_scanout.despan(), + render_device, + inputs, + idle, + }) + } +} 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/idle.rs b/toml-config/src/config/parsers/idle.rs new file mode 100644 index 00000000..b1821805 --- /dev/null +++ b/toml-config/src/config/parsers/idle.rs @@ -0,0 +1,45 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{n64, opt, Extractor, ExtractorError}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + std::time::Duration, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum IdleParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), +} + +pub struct IdleParser<'a>(pub &'a Context<'a>); + +impl Parser for IdleParser<'_> { + type Value = Duration; + type Error = IdleParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (minutes, seconds) = ext.extract((opt(n64("minutes")), opt(n64("seconds"))))?; + let idle = Duration::from_secs( + minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(), + ); + Ok(idle) + } +} 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..a3ba4adb --- /dev/null +++ b/toml-config/src/lib.rs @@ -0,0 +1,819 @@ +#![allow(clippy::len_zero, clippy::single_char_pattern, clippy::collapsible_if)] + +mod config; +mod toml; + +use { + crate::config::{ + parse_config, Action, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap, + ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch, + SimpleCommand, Status, Theme, + }, + ahash::{AHashMap, AHashSet}, + error_reporter::Report, + jay_config::{ + config, config_dir, + exec::{set_env, unset_env, Command}, + get_workspace, + input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat}, + is_reload, + keyboard::{Keymap, ModifiedKeySym}, + logging::set_log_level, + on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, set_idle, + status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, + switch_to_vt, + theme::{reset_colors, reset_font, reset_sizes, set_font}, + video::{ + connectors, drm_devices, on_connector_connected, on_graphics_initialized, + on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api, + Connector, DrmDevice, + }, + }, + std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc}, +}; + +fn default_seat() -> Seat { + get_seat("default") +} + +impl Action { + fn into_fn(self, state: &Rc) -> Box { + let s = state.persistent.seat; + match self { + Action::SimpleCommand { cmd } => match cmd { + SimpleCommand::Focus(dir) => Box::new(move || s.focus(dir)), + SimpleCommand::Move(dir) => Box::new(move || s.move_(dir)), + SimpleCommand::Split(axis) => Box::new(move || s.create_split(axis)), + SimpleCommand::ToggleSplit => Box::new(move || s.toggle_split()), + SimpleCommand::ToggleMono => Box::new(move || s.toggle_mono()), + SimpleCommand::ToggleFullscreen => Box::new(move || s.toggle_fullscreen()), + SimpleCommand::FocusParent => Box::new(move || s.focus_parent()), + SimpleCommand::Close => Box::new(move || s.close()), + SimpleCommand::DisablePointerConstraint => { + Box::new(move || s.disable_pointer_constraint()) + } + SimpleCommand::ToggleFloating => Box::new(move || s.toggle_floating()), + SimpleCommand::Quit => Box::new(quit), + SimpleCommand::ReloadConfigToml => { + let persistent = state.persistent.clone(); + Box::new(move || load_config(false, &persistent)) + } + SimpleCommand::ReloadConfigSo => Box::new(reload), + SimpleCommand::None => Box::new(|| ()), + }, + Action::Multi { actions } => { + let mut actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect(); + Box::new(move || { + for action in &mut actions { + action(); + } + }) + } + Action::Exec { exec } => Box::new(move || create_command(&exec).spawn()), + Action::SwitchToVt { num } => Box::new(move || switch_to_vt(num)), + Action::ShowWorkspace { name } => { + let workspace = get_workspace(&name); + Box::new(move || s.show_workspace(workspace)) + } + Action::ConfigureConnector { con } => Box::new(move || { + for c in connectors() { + if con.match_.matches(c) { + con.apply(c); + } + } + }), + Action::ConfigureInput { input } => { + let state = state.clone(); + Box::new(move || { + for c in input_devices() { + if input.match_.matches(c, &state) { + input.apply(c); + } + } + }) + } + Action::ConfigureOutput { out } => { + let state = state.clone(); + Box::new(move || { + for c in connectors() { + if out.match_.matches(c, &state) { + out.apply(c); + } + } + }) + } + Action::SetEnv { env } => Box::new(move || { + for (k, v) in &env { + set_env(k, v); + } + }), + Action::UnsetEnv { env } => Box::new(move || { + for k in &env { + unset_env(k); + } + }), + Action::SetKeymap { map } => { + let state = state.clone(); + Box::new(move || state.set_keymap(&map)) + } + Action::SetStatus { status } => { + let state = state.clone(); + Box::new(move || state.set_status(&status)) + } + Action::SetTheme { theme } => { + let state = state.clone(); + Box::new(move || state.apply_theme(&theme)) + } + Action::SetLogLevel { level } => Box::new(move || set_log_level(level)), + Action::SetGfxApi { api } => Box::new(move || set_gfx_api(api)), + Action::ConfigureDirectScanout { enabled } => { + Box::new(move || set_direct_scanout_enabled(enabled)) + } + Action::ConfigureDrmDevice { dev } => { + let state = state.clone(); + Box::new(move || { + for d in drm_devices() { + if dev.match_.matches(d, &state) { + dev.apply(d); + } + } + }) + } + Action::SetRenderDevice { dev } => { + let state = state.clone(); + Box::new(move || { + for d in drm_devices() { + if dev.matches(d, &state) { + d.make_render_device(); + } + } + }) + } + Action::ConfigureIdle { idle } => Box::new(move || set_idle(Some(idle))), + } + } +} + +fn apply_recursive_match<'a, U>( + type_name: &str, + list: &'a AHashMap, + 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); + } + if let Some(idle) = config.idle { + set_idle(Some(idle)); + } + } + on_devices_enumerated({ + let state = state.clone(); + move || { + if let Some(dev) = config.render_device { + for d in drm_devices() { + if dev.matches(d, &state) { + d.make_render_device(); + return; + } + } + } + } + }); + reset_colors(); + reset_font(); + reset_sizes(); + state.apply_theme(&config.theme); + if let Some(api) = config.gfx_api { + set_gfx_api(api); + } + if let Some(dse) = config.direct_scanout_enabled { + set_direct_scanout_enabled(dse); + } + on_new_drm_device({ + let state = state.clone(); + move |d| { + for dev in &config.drm_devices { + if dev.match_.matches(d, &state) { + dev.apply(d); + } + } + } + }); + on_new_input_device({ + let state = state.clone(); + move |c| { + for input in &config.inputs { + if input.match_.matches(c, &state) { + input.apply(c); + } + } + } + }); +} + +fn create_command(exec: &Exec) -> Command { + let mut command = Command::new(&exec.prog); + for arg in &exec.args { + command.arg(arg); + } + for (k, v) in &exec.envs { + command.env(k, v); + } + command +} + +const DEFAULT: &[u8] = include_bytes!("default-config.toml"); + +pub fn configure() { + let default = parse_config(DEFAULT, |e| { + panic!("Could not parse the default config: {}", Report::new(e)) + }); + let persistent = Rc::new(PersistentState { + seen_outputs: Default::default(), + default: default.unwrap(), + seat: default_seat(), + binds: Default::default(), + }); + load_config(true, &persistent); +} + +config!(configure); 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..9301b6bd --- /dev/null +++ b/toml-spec/spec/spec.generated.json @@ -0,0 +1,1100 @@ +{ + "$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": "An `Action` is an action performed by the compositor.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", + "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": "Configures the idle timeout.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-l = { type = \"configure-idle\", idle.minutes = 0 }\n alt-r = { type = \"configure-idle\", idle.minutes = 10 }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "configure-idle" + }, + "idle": { + "description": "The idle timeout.", + "$ref": "#/$defs/Idle" + } + }, + "required": [ + "type", + "idle" + ] + }, + { + "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" + }, + "idle": { + "description": "The configuration of the idle timeout.\n\nChanging thise field after compositor startup has no effect. Use `jay idle`\nor a `configure-idle` action to change the idle timeout at runtime.\n\n- Example:\n\n ```toml\n idle.minutes = 10\n ```\n", + "$ref": "#/$defs/Idle" + } + }, + "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" + ] + }, + "Idle": { + "description": "The definition of an idle timeout.\n\nOmitted values are set to 0. If all values are 0, the idle timeout is disabled.\n\n- Example:\n\n ```toml\n idle.minutes = 10\n ```\n", + "type": "object", + "properties": { + "minutes": { + "type": "integer", + "description": "The number of minutes before going idle.", + "minimum": 0.0 + }, + "seconds": { + "type": "integer", + "description": "The number of seconds before going idle.", + "minimum": 0.0 + } + }, + "required": [] + }, + "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..28bc1d07 --- /dev/null +++ b/toml-spec/spec/spec.generated.md @@ -0,0 +1,2302 @@ +# 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` + +An `Action` is an action performed by the compositor. + +- Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + +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-idle`: + + Configures the idle timeout. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-idle", idle.minutes = 0 } + alt-r = { type = "configure-idle", idle.minutes = 10 } + ``` + + The table has the following fields: + + - `idle` (required): + + The idle timeout. + + The value of this field should be a [Idle](#types-Idle). + +- `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). + +- `idle` (optional): + + The configuration of the idle timeout. + + Changing thise field after compositor startup has no effect. Use `jay idle` + or a `configure-idle` action to change the idle timeout at runtime. + + - Example: + + ```toml + idle.minutes = 10 + ``` + + The value of this field should be a [Idle](#types-Idle). + + + +### `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. + + + + +### `Idle` + +The definition of an idle timeout. + +Omitted values are set to 0. If all values are 0, the idle timeout is disabled. + +- Example: + + ```toml + idle.minutes = 10 + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `minutes` (optional): + + The number of minutes before going idle. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + +- `seconds` (optional): + + The number of seconds before going idle. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + + + +### `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..75822054 --- /dev/null +++ b/toml-spec/spec/spec.yaml @@ -0,0 +1,1936 @@ +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: | + An `Action` is an action performed by the compositor. + + - Example: + + ```toml + [shortcuts] + alt-q = "quit" + ``` + 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-idle: + description: | + Configures the idle timeout. + + - Example: + + ```toml + [shortcuts] + alt-l = { type = "configure-idle", idle.minutes = 0 } + alt-r = { type = "configure-idle", idle.minutes = 10 } + ``` + fields: + idle: + description: The idle timeout. + required: true + ref: Idle + 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" } + ``` + idle: + ref: Idle + required: false + description: | + The configuration of the idle timeout. + + Changing thise field after compositor startup has no effect. Use `jay idle` + or a `configure-idle` action to change the idle timeout at runtime. + + - Example: + + ```toml + idle.minutes = 10 + ``` + + +Idle: + kind: table + description: | + The definition of an idle timeout. + + Omitted values are set to 0. If all values are 0, the idle timeout is disabled. + + - Example: + + ```toml + idle.minutes = 10 + ``` + fields: + minutes: + description: The number of minutes before going idle. + kind: number + integer_only: true + minimum: 0 + required: false + seconds: + description: The number of seconds before going idle. + kind: number + integer_only: true + minimum: 0 + required: false 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) + } +}