1
0
Fork 0
forked from wry/wry

Merge pull request #131 from mahkoh/jorth/toml

config: change default config to use toml-based configuration
This commit is contained in:
mahkoh 2024-03-16 05:00:16 +01:00 committed by GitHub
commit a4559f5293
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 15094 additions and 581 deletions

View file

@ -6,6 +6,8 @@ tasks:
sudo pacman -Syu --noconfirm
sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake
rustup toolchain install stable
cd jay
git submodule update --init
- test: |
cd jay
cargo test

31
.github/workflows/toml-spec.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: toml-spec
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Show env
run: |
uname -a
ldd --version
- name: Install
run: |
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
rustup toolchain install nightly --allow-downgrade -c rustfmt
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Check
run: |
cd toml-spec
cargo run
git diff --exit-code

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
.*
!.gitignore
!.gitmodules
!/.cargo
!/.builds
!/.github

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "toml-config/toml-test"]
path = toml-config/toml-test
url = https://github.com/mahkoh/toml-tests.git

245
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

@ -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"

View file

@ -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);

View file

@ -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";

View file

@ -506,6 +506,14 @@ impl Client {
self.send(&ClientMessage::SetEnv { key, val });
}
pub fn set_log_level(&self, level: LogLevel) {
self.send(&ClientMessage::SetLogLevel { level })
}
pub fn unset_env(&self, key: &str) {
self.send(&ClientMessage::UnsetEnv { key });
}
pub fn set_status(&self, status: &str) {
self.send(&ClientMessage::SetStatus { status });
}
@ -576,6 +584,16 @@ impl Client {
self.send(&ClientMessage::SetDoubleClickDistance { dist });
}
pub fn disable_default_seat(&self) {
self.send(&ClientMessage::DisableDefaultSeat);
}
pub fn connector_get_position(&self, connector: Connector) -> (i32, i32) {
let res = self.send_with_response(&ClientMessage::ConnectorGetPosition { connector });
get_response!(res, (0, 0), ConnectorGetPosition { x, y });
(x, y)
}
pub fn connector_set_position(&self, connector: Connector, x: i32, y: i32) {
self.send(&ClientMessage::ConnectorSetPosition { connector, x, y });
}
@ -591,9 +609,49 @@ impl Client {
});
}
pub fn device_connectors(&self, device: DrmDevice) -> Vec<Connector> {
let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device });
get_response!(res, vec![], GetDeviceConnectors { connectors });
pub fn connector_get_name(&self, connector: Connector) -> String {
let res = self.send_with_response(&ClientMessage::GetConnectorName { connector });
get_response!(res, String::new(), GetConnectorName { name });
name
}
pub fn connector_get_model(&self, connector: Connector) -> String {
let res = self.send_with_response(&ClientMessage::GetConnectorModel { connector });
get_response!(res, String::new(), GetConnectorModel { model });
model
}
pub fn connector_get_manufacturer(&self, connector: Connector) -> String {
let res = self.send_with_response(&ClientMessage::GetConnectorManufacturer { connector });
get_response!(
res,
String::new(),
GetConnectorManufacturer { manufacturer }
);
manufacturer
}
pub fn connector_get_serial_number(&self, connector: Connector) -> String {
let res = self.send_with_response(&ClientMessage::GetConnectorSerialNumber { connector });
get_response!(
res,
String::new(),
GetConnectorSerialNumber { serial_number }
);
serial_number
}
pub fn connectors(&self, device: Option<DrmDevice>) -> Vec<Connector> {
if let Some(device) = device {
let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device });
get_response!(res, vec![], GetConnectors { connectors });
return connectors;
}
let res = self.send_with_response(&ClientMessage::GetConnectors {
device,
connected_only: false,
});
get_response!(res, vec![], GetConnectors { connectors });
connectors
}
@ -603,6 +661,12 @@ impl Client {
syspath
}
pub fn drm_device_devnode(&self, device: DrmDevice) -> String {
let res = self.send_with_response(&ClientMessage::GetDrmDeviceDevnode { device });
get_response!(res, String::new(), GetDrmDeviceDevnode { devnode });
devnode
}
pub fn drm_device_vendor(&self, device: DrmDevice) -> String {
let res = self.send_with_response(&ClientMessage::GetDrmDeviceVendor { device });
get_response!(res, String::new(), GetDrmDeviceVendor { vendor });
@ -723,6 +787,22 @@ impl Client {
self.on_devices_enumerated.set(Some(Box::new(f)));
}
pub fn config_dir(&self) -> String {
let res = self.send_with_response(&ClientMessage::GetConfigDir);
get_response!(res, String::new(), GetConfigDir { dir });
dir
}
pub fn workspaces(&self) -> Vec<Workspace> {
let res = self.send_with_response(&ClientMessage::GetWorkspaces);
get_response!(res, vec![], GetWorkspaces { workspaces });
workspaces
}
pub fn set_idle(&self, timeout: Duration) {
self.send(&ClientMessage::SetIdle { timeout })
}
pub fn set_seat(&self, device: InputDevice, seat: Seat) {
self.send(&ClientMessage::SetSeat { device, seat })
}
@ -772,12 +852,28 @@ impl Client {
name
}
pub fn input_device_syspath(&self, device: InputDevice) -> String {
let res = self.send_with_response(&ClientMessage::GetInputDeviceSyspath { device });
get_response!(res, String::new(), GetInputDeviceSyspath { syspath });
syspath
}
pub fn input_device_devnode(&self, device: InputDevice) -> String {
let res = self.send_with_response(&ClientMessage::GetInputDeviceDevnode { device });
get_response!(res, String::new(), GetInputDeviceDevnode { devnode });
devnode
}
pub fn has_capability(&self, device: InputDevice, cap: Capability) -> bool {
let res = self.send_with_response(&ClientMessage::HasCapability { device, cap });
get_response!(res, false, HasCapability { has });
has
}
pub fn destroy_keymap(&self, keymap: Keymap) {
self.send(&ClientMessage::DestroyKeymap { keymap })
}
pub fn seat_set_keymap(&self, seat: Seat, keymap: Keymap) {
self.send(&ClientMessage::SeatSetKeymap { seat, keymap })
}

View file

@ -381,6 +381,49 @@ pub enum ClientMessage<'a> {
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
},
DisableDefaultSeat,
DestroyKeymap {
keymap: Keymap,
},
GetConnectorName {
connector: Connector,
},
GetConnectorModel {
connector: Connector,
},
GetConnectorManufacturer {
connector: Connector,
},
GetConnectorSerialNumber {
connector: Connector,
},
GetConnectors {
device: Option<DrmDevice>,
connected_only: bool,
},
ConnectorGetPosition {
connector: Connector,
},
GetConfigDir,
GetWorkspaces,
UnsetEnv {
key: &'a str,
},
SetLogLevel {
level: LogLevel,
},
GetDrmDeviceDevnode {
device: DrmDevice,
},
GetInputDeviceSyspath {
device: InputDevice,
},
GetInputDeviceDevnode {
device: InputDevice,
},
SetIdle {
timeout: Duration,
},
}
#[derive(Serialize, Deserialize, Debug)]
@ -440,7 +483,7 @@ pub enum Response {
GetFullscreen {
fullscreen: bool,
},
GetDeviceConnectors {
GetConnectors {
connectors: Vec<Connector>,
},
GetDrmDeviceSyspath {
@ -489,6 +532,37 @@ pub enum Response {
AddPollable {
id: Result<PollableId, String>,
},
GetConnectorName {
name: String,
},
GetConnectorModel {
model: String,
},
GetConnectorManufacturer {
manufacturer: String,
},
GetConnectorSerialNumber {
serial_number: String,
},
ConnectorGetPosition {
x: i32,
y: i32,
},
GetConfigDir {
dir: String,
},
GetWorkspaces {
workspaces: Vec<Workspace>,
},
GetDrmDeviceDevnode {
devnode: String,
},
GetInputDeviceSyspath {
syspath: String,
},
GetInputDeviceDevnode {
devnode: String,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -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,

View file

@ -8,6 +8,7 @@ use {
input::{acceleration::AccelProfile, capability::Capability},
keyboard::Keymap,
Axis, Direction, ModifiedKeySym, Workspace,
_private::DEFAULT_SEAT_NAME,
},
serde::{Deserialize, Serialize},
std::time::Duration,
@ -112,6 +113,20 @@ impl InputDevice {
pub fn set_natural_scrolling_enabled(self, enabled: bool) {
get!().set_input_natural_scrolling_enabled(self, enabled);
}
/// Returns the syspath of this device.
///
/// E.g. `/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.3/5-1.1.3:1.0`.
pub fn syspath(self) -> String {
get!(String::new()).input_device_syspath(self)
}
/// Returns the devnode of this device.
///
/// E.g. `/dev/input/event7`.
pub fn devnode(self) -> String {
get!(String::new()).input_device_devnode(self)
}
}
/// A seat.
@ -319,10 +334,20 @@ pub fn input_devices() -> Vec<InputDevice> {
/// Returns or creates a seat.
///
/// Seats are identified by their name. If no seat with the name exists, a new seat will be created.
///
/// NOTE: You should prefer [`get_default_seat`] instead. Most applications cannot handle more than
/// one seat and will only process input from one of the seats.
pub fn get_seat(name: &str) -> Seat {
get!(Seat(0)).get_seat(name)
}
/// Returns or creates the default seat.
///
/// This is equivalent to `get_seat("default")`.
pub fn get_default_seat() -> Seat {
get_seat(DEFAULT_SEAT_NAME)
}
/// Sets a closure to run when a new seat has been created.
pub fn on_new_seat<F: FnMut(Seat) + 'static>(f: F) {
get!().on_new_seat(f)
@ -357,3 +382,14 @@ pub fn set_double_click_time(duration: Duration) {
pub fn set_double_click_distance(distance: i32) {
get!().set_double_click_distance(distance)
}
/// Disables the creation of a default seat.
///
/// Unless this function is called at startup of the compositor, a seat called `default`
/// will automatically be created.
///
/// When a new input device is attached and a seat called `default` exists, the input
/// device is initially attached to this seat.
pub fn disable_default_seat() {
get!().disable_default_seat();
}

View file

@ -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.

View file

@ -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);

View file

@ -12,23 +12,19 @@
//! config!(configure);
//! ```
//!
//! This configuration will not allow you to interact with the compositor at all nor exit it.
//! This configuration will not allow you to exit the compositor.
//! To add at least that much functionality, add the following code to `configure`:
//!
//! ```rust
//! use jay_config::{config, quit};
//! use jay_config::input::{get_seat, input_devices, on_new_input_device};
//! use jay_config::input::{get_default_seat, input_devices, on_new_input_device};
//! use jay_config::keyboard::mods::ALT;
//! use jay_config::keyboard::syms::SYM_q;
//!
//! fn configure() {
//! // Create a seat.
//! let seat = get_seat("default");
//! let seat = get_default_seat();
//! // Create a key binding to exit the compositor.
//! seat.bind(ALT | SYM_q, || quit());
//! // Assign all current and future input devices to this seat.
//! input_devices().into_iter().for_each(move |d| d.set_seat(seat));
//! on_new_input_device(move |d| d.set_seat(seat));
//! }
//!
//! config!(configure);
@ -46,7 +42,10 @@
use {
crate::keyboard::ModifiedKeySym,
serde::{Deserialize, Serialize},
std::fmt::{Debug, Display, Formatter},
std::{
fmt::{Debug, Display, Formatter},
time::Duration,
},
};
#[macro_use]
@ -199,3 +198,20 @@ pub fn on_idle<F: FnMut() + 'static>(f: F) {
pub fn on_devices_enumerated<F: FnOnce() + 'static>(f: F) {
get!().on_devices_enumerated(f)
}
/// Returns the Jay config directory.
pub fn config_dir() -> String {
get!().config_dir()
}
/// Returns all visible workspaces.
pub fn workspaces() -> Vec<Workspace> {
get!().workspaces()
}
/// Configures the idle timeout.
///
/// `None` disables the timeout.
pub fn set_idle(timeout: Option<Duration>) {
get!().set_idle(timeout.unwrap_or_default())
}

View file

@ -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);
}

View file

@ -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 {

View file

@ -178,6 +178,14 @@ impl Connector {
self.mode().refresh_millihz
}
/// Retrieves the position of the output in the global compositor space.
pub fn position(self) -> (i32, i32) {
if !self.connected() {
return (0, 0);
}
get!().connector_get_position(self)
}
/// Sets the position of the connector in the global compositor space.
///
/// `x` and `y` must be non-negative and must not exceed a currently unspecified limit.
@ -212,6 +220,34 @@ impl Connector {
}
get!().connector_set_transform(self, transform);
}
pub fn name(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_name(self)
}
pub fn model(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_model(self)
}
pub fn manufacturer(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_manufacturer(self)
}
pub fn serial_number(self) -> String {
if !self.exists() {
return String::new();
}
get!(String::new()).connector_get_serial_number(self)
}
}
/// Returns all available DRM devices.
@ -247,6 +283,10 @@ pub fn on_graphics_initialized<F: FnOnce() + 'static>(f: F) {
get!().on_graphics_initialized(f)
}
pub fn connectors() -> Vec<Connector> {
get!().connectors(None)
}
/// Returns the connector with the given id.
///
/// The linux kernel identifies connectors by a (type, idx) tuple, e.g., `DP-0`.
@ -381,7 +421,14 @@ pub struct DrmDevice(pub u64);
impl DrmDevice {
/// Returns the connectors of this device.
pub fn connectors(self) -> Vec<Connector> {
get!().device_connectors(self)
get!().connectors(Some(self))
}
/// Returns the devnode of this device.
///
/// E.g. `/dev/dri/card0`.
pub fn devnode(self) -> String {
get!().drm_device_devnode(self)
}
/// Returns the syspath of this device.

View file

@ -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()

View file

@ -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,

View file

@ -16,7 +16,10 @@ use {
dbus::Dbus,
forker,
globals::Globals,
ifs::{wl_output::WlOutputGlobal, wl_surface::NoneSurfaceExt},
ifs::{
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
wl_surface::NoneSurfaceExt,
},
io_uring::{IoUring, IoUringError},
leaks,
logger::Logger,
@ -39,7 +42,7 @@ use {
},
ahash::AHashSet,
forker::ForkerProxy,
jay_config::video::GfxApi,
jay_config::{_private::DEFAULT_SEAT_NAME, video::GfxApi},
std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration},
thiserror::Error,
uapi::c,
@ -204,9 +207,10 @@ fn start_compositor2(
dma_buf_ids: Default::default(),
drm_feedback_ids: Default::default(),
direct_scanout_enabled: Cell::new(true),
output_transforms: Default::default(),
persistent_output_states: Default::default(),
double_click_interval_usec: Cell::new(400 * 1000),
double_click_distance: Cell::new(5),
create_default_seat: Cell::new(true),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -254,6 +258,10 @@ async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
config.configure(false);
state.config.set(Some(Rc::new(config)));
if state.create_default_seat.get() && state.globals.seats.is_empty() {
state.create_seat(DEFAULT_SEAT_NAME);
}
let _geh = start_global_event_handlers(&state, &backend);
state.start_xwayland();
@ -359,6 +367,17 @@ fn init_fd_limit() {
}
fn create_dummy_output(state: &Rc<State>) {
let output_id = Rc::new(OutputId {
connector: "jay-dummy-connector".to_string(),
manufacturer: "jay".to_string(),
model: "jay-dummy-output".to_string(),
serial_number: "".to_string(),
});
let persistent_state = Rc::new(PersistentOutputState {
transform: Default::default(),
scale: Default::default(),
pos: Default::default(),
});
let dummy_output = Rc::new(OutputNode {
id: state.node_ids.next(),
global: Rc::new(WlOutputGlobal::new(
@ -374,18 +393,16 @@ fn create_dummy_output(state: &Rc<State>) {
drm_dev: None,
async_event: Default::default(),
}),
0,
Vec::new(),
&backend::Mode {
width: 0,
height: 0,
refresh_rate_millihz: 0,
},
"jay",
"dummy-output",
"0",
0,
0,
&output_id,
&persistent_state,
)),
jay_outputs: Default::default(),
workspaces: Default::default(),

View file

@ -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)
}

View file

@ -164,9 +164,7 @@ impl ConfigProxyHandler {
return;
}
}
let global_name = self.state.globals.name();
let seat = WlSeatGlobal::new(global_name, name, &self.state);
self.state.globals.add_global(&self.state, &seat);
let seat = self.state.create_seat(name);
self.respond(Response::GetSeat {
seat: Seat(seat.id().raw() as _),
});
@ -185,13 +183,26 @@ impl ConfigProxyHandler {
res
}
fn handle_get_drm_device_connectors(&self, dev: DrmDevice) -> Result<(), CphError> {
let dev = self.get_drm_device(dev)?;
let mut connectors = vec![];
for c in dev.connectors.lock().values() {
connectors.push(Connector(c.connector.id().raw() as _));
fn handle_get_connectors(
&self,
dev: Option<DrmDevice>,
connected_only: bool,
) -> Result<(), CphError> {
let datas: Vec<_>;
if let Some(dev) = dev {
let dev = self.get_drm_device(dev)?;
datas = dev.connectors.lock().values().cloned().collect();
} else {
datas = self.state.connectors.lock().values().cloned().collect();
}
self.respond(Response::GetDeviceConnectors { connectors });
let connectors = datas
.iter()
.flat_map(|d| match (connected_only, d.connected.get()) {
(false, _) | (true, true) => Some(Connector(d.connector.id().raw() as _)),
_ => None,
})
.collect();
self.respond(Response::GetConnectors { connectors });
Ok(())
}
@ -202,6 +213,13 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_drm_device_devnode(&self, dev: DrmDevice) -> Result<(), CphError> {
let dev = self.get_drm_device(dev)?;
let devnode = dev.devnode.clone().unwrap_or_default();
self.respond(Response::GetDrmDeviceDevnode { devnode });
Ok(())
}
fn handle_get_drm_device_vendor(&self, dev: DrmDevice) -> Result<(), CphError> {
let dev = self.get_drm_device(dev)?;
let vendor = dev.vendor.clone().unwrap_or_default();
@ -306,6 +324,35 @@ impl ConfigProxyHandler {
}
}
fn handle_unset_env(&self, key: &str) {
if let Some(f) = self.state.forker.get() {
f.unsetenv(key.as_bytes());
}
}
fn handle_get_config_dir(&self) {
let dir = self.state.config_dir.clone().unwrap_or_default();
self.respond(Response::GetConfigDir { dir });
}
fn handle_get_workspaces(&self) {
let mut workspaces = vec![];
for ws in self.state.workspaces.lock().values() {
let id = match self.workspaces_by_name.get(&ws.name) {
None => {
let id = self.workspace_ids.fetch_add(1);
let name = Rc::new(ws.name.clone());
self.workspaces_by_name.set(name.clone(), id);
self.workspaces_by_id.set(id, name);
id
}
Some(id) => id,
};
workspaces.push(Workspace(id));
}
self.respond(Response::GetWorkspaces { workspaces });
}
fn handle_program_timer(
&self,
timer: JayTimer,
@ -690,6 +737,26 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_input_device_syspath(&self, device: InputDevice) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
self.respond(Response::GetInputDeviceSyspath {
syspath: dev.syspath.clone().unwrap_or_default(),
});
Ok(())
}
fn handle_get_input_device_devnode(&self, device: InputDevice) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
self.respond(Response::GetInputDeviceDevnode {
devnode: dev.devnode.clone().unwrap_or_default(),
});
Ok(())
}
fn handle_set_idle(&self, timeout: Duration) {
self.state.idle.set_timeout(timeout);
}
fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_connector(connector)?;
self.respond(Response::ConnectorConnected {
@ -749,6 +816,38 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_connector(connector)?;
self.respond(Response::GetConnectorName {
name: connector.name.clone(),
});
Ok(())
}
fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_output(connector)?;
self.respond(Response::GetConnectorModel {
model: connector.monitor_info.product.clone(),
});
Ok(())
}
fn handle_connector_manufacturer(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_output(connector)?;
self.respond(Response::GetConnectorManufacturer {
manufacturer: connector.monitor_info.manufacturer.clone(),
});
Ok(())
}
fn handle_connector_serial_number(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_output(connector)?;
self.respond(Response::GetConnectorSerialNumber {
serial_number: connector.monitor_info.serial_number.clone(),
});
Ok(())
}
fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
if size < 0 {
@ -795,7 +894,7 @@ impl ConfigProxyHandler {
fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_output(connector)?;
self.respond(Response::ConnectorGetScale {
scale: connector.node.global.preferred_scale.get().to_f64(),
scale: connector.node.global.persistent.scale.get().to_f64(),
});
Ok(())
}
@ -850,6 +949,13 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_connector_get_position(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_output(connector)?;
let (x, y) = connector.node.global.pos.get().position();
self.respond(Response::ConnectorGetPosition { x, y });
Ok(())
}
fn handle_connector_set_enabled(
&self,
connector: Connector,
@ -1013,6 +1119,19 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_log_level(&self, level: LogLevel) {
let level = match level {
LogLevel::Error => Level::Error,
LogLevel::Warn => Level::Warn,
LogLevel::Info => Level::Info,
LogLevel::Debug => Level::Debug,
LogLevel::Trace => Level::Trace,
};
if let Some(logger) = &self.state.logger {
logger.set_level(level);
}
}
fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> {
let kb = self.get_kb(kb)?;
kb.grab(grab);
@ -1252,6 +1371,10 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_destroy_keymap(&self, keymap: Keymap) {
self.keymaps.remove(&keymap);
}
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
if let Err(e) = self.handle_request_(msg) {
log::error!("Could not handle client request: {}", ErrorFmt(e));
@ -1401,7 +1524,7 @@ impl ConfigProxyHandler {
}
ClientMessage::Reload => self.handle_reload(),
ClientMessage::GetDeviceConnectors { device } => self
.handle_get_drm_device_connectors(device)
.handle_get_connectors(Some(device), false)
.wrn("get_device_connectors")?,
ClientMessage::GetDrmDeviceSyspath { device } => self
.handle_get_drm_device_syspath(device)
@ -1516,6 +1639,43 @@ impl ConfigProxyHandler {
env,
fds,
} => self.handle_run(prog, args, env, fds).wrn("run")?,
ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false),
ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap),
ClientMessage::GetConnectorName { connector } => self
.handle_connector_name(connector)
.wrn("connector_name")?,
ClientMessage::GetConnectorModel { connector } => self
.handle_connector_model(connector)
.wrn("connector_model")?,
ClientMessage::GetConnectorManufacturer { connector } => self
.handle_connector_manufacturer(connector)
.wrn("connector_manufacturer")?,
ClientMessage::GetConnectorSerialNumber { connector } => self
.handle_connector_serial_number(connector)
.wrn("connector_serial_number")?,
ClientMessage::GetConnectors {
device,
connected_only,
} => self
.handle_get_connectors(device, connected_only)
.wrn("get_connectors")?,
ClientMessage::ConnectorGetPosition { connector } => self
.handle_connector_get_position(connector)
.wrn("connector_get_position")?,
ClientMessage::GetConfigDir => self.handle_get_config_dir(),
ClientMessage::GetWorkspaces => self.handle_get_workspaces(),
ClientMessage::UnsetEnv { key } => self.handle_unset_env(key),
ClientMessage::SetLogLevel { level } => self.handle_set_log_level(level),
ClientMessage::GetDrmDeviceDevnode { device } => self
.handle_get_drm_device_devnode(device)
.wrn("get_drm_device_devnode")?,
ClientMessage::GetInputDeviceSyspath { device } => self
.handle_get_input_device_syspath(device)
.wrn("get_input_device_syspath")?,
ClientMessage::GetInputDeviceDevnode { device } => self
.handle_get_input_device_devnode(device)
.wrn("get_input_device_devnode")?,
ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout),
}
Ok(())
}

View file

@ -316,7 +316,7 @@ impl dyn GfxFramebuffer {
scale,
render_hardware_cursor,
node.has_fullscreen(),
node.global.transform.get(),
node.global.persistent.transform.get(),
)
}

View file

@ -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 _,

View file

@ -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(())
}

View file

@ -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,

View file

@ -13,7 +13,7 @@ use {
rect::Rect,
state::{ConnectorData, State},
time::Time,
tree::OutputNode,
tree::{calculate_logical_size, OutputNode},
utils::{
buffd::{MsgParser, MsgParserError},
clonecell::CloneCell,
@ -75,12 +75,18 @@ pub struct WlOutputGlobal {
pub pending_captures: LinkedList<Rc<ZwlrScreencopyFrameV1>>,
pub destroyed: Cell<bool>,
pub legacy_scale: Cell<u32>,
pub preferred_scale: Cell<crate::scale::Scale>,
pub persistent: Rc<PersistentOutputState>,
}
pub struct PersistentOutputState {
pub transform: Cell<Transform>,
pub scale: Cell<crate::scale::Scale>,
pub pos: Cell<(i32, i32)>,
}
#[derive(Eq, PartialEq, Hash)]
pub struct OutputId {
pub connector: String,
pub manufacturer: String,
pub model: String,
pub serial_number: String,
@ -96,33 +102,26 @@ impl WlOutputGlobal {
name: GlobalName,
state: &Rc<State>,
connector: &Rc<ConnectorData>,
x1: i32,
modes: Vec<backend::Mode>,
mode: &backend::Mode,
manufacturer: &str,
product: &str,
serial_number: &str,
width_mm: i32,
height_mm: i32,
output_id: &Rc<OutputId>,
persistent_state: &Rc<PersistentOutputState>,
) -> Self {
let output_id = Rc::new(OutputId {
manufacturer: manufacturer.to_string(),
model: product.to_string(),
serial_number: serial_number.to_string(),
});
let transform = state
.output_transforms
.borrow()
.get(&output_id)
.copied()
.unwrap_or(Transform::None);
let (width, height) = transform.maybe_swap((mode.width, mode.height));
let (x, y) = persistent_state.pos.get();
let scale = persistent_state.scale.get();
let (width, height) = calculate_logical_size(
(mode.width, mode.height),
persistent_state.transform.get(),
scale,
);
Self {
name,
state: state.clone(),
connector: connector.clone(),
pos: Cell::new(Rect::new_sized(x1, 0, width, height).unwrap()),
output_id,
pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()),
output_id: output_id.clone(),
mode: Cell::new(*mode),
modes,
node: Default::default(),
@ -132,9 +131,8 @@ impl WlOutputGlobal {
unused_captures: Default::default(),
pending_captures: Default::default(),
destroyed: Cell::new(false),
legacy_scale: Cell::new(1),
preferred_scale: Cell::new(crate::scale::Scale::from_int(1)),
transform: Cell::new(transform),
legacy_scale: Cell::new(scale.round_up()),
persistent: persistent_state.clone(),
}
}
@ -289,7 +287,10 @@ impl WlOutputGlobal {
pub fn pixel_size(&self) -> (i32, i32) {
let mode = self.mode.get();
self.transform.get().maybe_swap((mode.width, mode.height))
self.persistent
.transform
.get()
.maybe_swap((mode.width, mode.height))
}
}
@ -336,7 +337,7 @@ impl WlOutput {
subpixel: SP_UNKNOWN,
make: &self.global.output_id.manufacturer,
model: &self.global.output_id.model,
transform: self.global.transform.get().to_wl(),
transform: self.global.persistent.transform.get().to_wl(),
};
self.client.event(event);
}

View file

@ -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 {

View file

@ -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 _,
});
}
}

View file

@ -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(),
});
}

View file

@ -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;

View file

@ -12,13 +12,13 @@ testcase!();
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let client = run.create_client().await?;
tassert!(client.registry.seats.is_empty());
tassert_eq!(client.registry.seats.len(), 1);
let seat = run.get_seat("default")?;
let seat = run.get_seat("new-seat")?;
client.sync().await;
tassert_eq!(client.registry.seats.len(), 1);
tassert_eq!(client.registry.seats.len(), 2);
let client_seat = client.registry.seats.get(&seat.name());
tassert!(client_seat.is_some());

View file

@ -852,7 +852,7 @@ impl UsrWlBufferOwner for GuiBuffer {
impl UsrWpFractionalScaleOwner for WindowData {
fn preferred_scale(self: Rc<Self>, ev: &PreferredScale) {
let mut layout = self.first_scale.replace(false);
let scale = Scale(ev.scale);
let scale = Scale::from_wl(ev.scale);
layout |= self.scale.replace(scale) != scale;
if layout {
self.layout();

View file

@ -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

View file

@ -5,7 +5,13 @@ const BASEF: f64 = BASE as f64;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Scale(pub u32);
pub struct Scale(u32);
impl Default for Scale {
fn default() -> Self {
Scale::from_int(1)
}
}
impl Scale {
pub fn from_int(f: u32) -> Self {
@ -23,6 +29,14 @@ impl Scale {
pub fn round_up(self) -> u32 {
self.0.saturating_add(BASE - 1) / BASE
}
pub fn from_wl(wl: u32) -> Self {
Self(wl)
}
pub fn to_wl(self) -> u32 {
self.0
}
}
impl PartialEq<u32> for Scale {

View file

@ -27,7 +27,7 @@ use {
jay_seat_events::JaySeatEvents,
jay_workspace_watcher::JayWorkspaceWatcher,
wl_drm::WlDrmGlobal,
wl_output::OutputId,
wl_output::{OutputId, PersistentOutputState},
wl_seat::{SeatIds, WlSeatGlobal},
wl_surface::{
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
@ -153,9 +153,10 @@ pub struct State {
pub dma_buf_ids: DmaBufIds,
pub drm_feedback_ids: DrmFeedbackIds,
pub direct_scanout_enabled: Cell<bool>,
pub output_transforms: RefCell<AHashMap<Rc<OutputId>, Transform>>,
pub persistent_output_states: CopyHashMap<Rc<OutputId>, Rc<PersistentOutputState>>,
pub double_click_interval_usec: Cell<u64>,
pub double_click_distance: Cell<i32>,
pub create_default_seat: Cell<bool>,
}
// impl Drop for State {
@ -215,14 +216,14 @@ pub struct InputDeviceData {
pub id: InputDeviceId,
pub data: Rc<DeviceHandlerData>,
pub async_event: Rc<AsyncEvent>,
pub syspath: Option<String>,
pub devnode: Option<String>,
}
pub struct DeviceHandlerData {
pub seat: CloneCell<Option<Rc<WlSeatGlobal>>>,
pub px_per_scroll_wheel: Cell<f64>,
pub device: Rc<dyn InputDevice>,
pub syspath: Option<String>,
pub devnode: Option<String>,
}
pub struct ConnectorData {
@ -771,7 +772,7 @@ impl State {
self,
Some(output.global.pos.get()),
Some(rr),
output.global.preferred_scale.get(),
output.global.persistent.scale.get(),
render_hw_cursor,
);
output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None);
@ -924,4 +925,11 @@ impl State {
capture.send_failed();
}
}
pub fn create_seat(self: &Rc<Self>, name: &str) -> Rc<WlSeatGlobal> {
let global_name = self.globals.name();
let seat = WlSeatGlobal::new(global_name, name, self);
self.globals.add_global(self, &seat);
seat
}
}

View file

@ -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);
}
}

View file

@ -6,14 +6,21 @@ use {
tasks::udev_utils::{udev_props, UdevProps},
utils::asyncevent::AsyncEvent,
},
jay_config::_private::DEFAULT_SEAT_NAME,
std::{cell::Cell, rc::Rc},
};
pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
let props = match dev.dev_t() {
None => UdevProps::default(),
Some(dev_t) => udev_props(dev_t, 3),
};
let data = Rc::new(DeviceHandlerData {
seat: Default::default(),
px_per_scroll_wheel: Cell::new(PX_PER_SCROLL),
device: dev.clone(),
syspath: props.syspath,
devnode: props.devnode,
});
let ae = Rc::new(AsyncEvent::default());
let oh = DeviceHandler {
@ -23,10 +30,6 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
ae: ae.clone(),
};
let handler = state.eng.spawn(oh.handle());
let props = match dev.dev_t() {
None => UdevProps::default(),
Some(dev_t) => udev_props(dev_t, 3),
};
state.input_device_handlers.borrow_mut().insert(
dev.id(),
InputDeviceData {
@ -34,8 +37,6 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
id: dev.id(),
data,
async_event: ae,
syspath: props.syspath,
devnode: props.devnode,
},
);
}
@ -53,6 +54,12 @@ impl DeviceHandler {
let ae = self.ae.clone();
self.dev.on_change(Rc::new(move || ae.trigger()));
}
for seat in self.state.globals.seats.lock().values() {
if seat.seat_name() == DEFAULT_SEAT_NAME {
self.data.seat.set(Some(seat.clone()));
break;
}
}
if let Some(config) = self.state.config.get() {
config.new_input_device(self.dev.id());
}

View file

@ -30,7 +30,7 @@ use {
},
utils::{
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
linkedlist::LinkedList, scroller::Scroller,
linkedlist::LinkedList, scroller::Scroller, transform_ext::TransformExt,
},
wire::{JayOutputId, JayScreencastId},
},
@ -120,7 +120,7 @@ impl OutputNode {
}
pub fn set_preferred_scale(self: &Rc<Self>, scale: Scale) {
let old_scale = self.global.preferred_scale.replace(scale);
let old_scale = self.global.persistent.scale.replace(scale);
if scale == old_scale {
return;
}
@ -161,7 +161,7 @@ impl OutputNode {
let font = self.state.theme.font.borrow_mut();
let theme = &self.state.theme;
let th = theme.sizes.title_height.get();
let scale = self.global.preferred_scale.get();
let scale = self.global.persistent.scale.get();
let scale = if scale != 1 {
Some(scale.to_f64())
} else {
@ -388,7 +388,7 @@ impl OutputNode {
}
pub fn update_mode(self: &Rc<Self>, mode: Mode) {
self.update_mode_and_transform(mode, self.global.transform.get());
self.update_mode_and_transform(mode, self.global.persistent.transform.get());
}
pub fn update_transform(self: &Rc<Self>, transform: Transform) {
@ -397,17 +397,13 @@ impl OutputNode {
pub fn update_mode_and_transform(self: &Rc<Self>, mode: Mode, transform: Transform) {
let old_mode = self.global.mode.get();
let old_transform = self.global.transform.get();
let old_transform = self.global.persistent.transform.get();
if (old_mode, old_transform) == (mode, transform) {
return;
}
let (old_width, old_height) = self.global.pixel_size();
self.global.mode.set(mode);
self.state
.output_transforms
.borrow_mut()
.insert(self.global.output_id.clone(), transform);
self.global.transform.set(transform);
self.global.persistent.transform.set(transform);
let (new_width, new_height) = self.global.pixel_size();
self.change_extents_(&self.calculate_extents());
@ -436,18 +432,18 @@ impl OutputNode {
}
fn calculate_extents(&self) -> Rect {
let (mut width, mut height) = self.global.pixel_size();
let scale = self.global.preferred_scale.get();
if scale != 1 {
let scale = scale.to_f64();
width = (width as f64 / scale).round() as _;
height = (height as f64 / scale).round() as _;
}
let mode = self.global.mode.get();
let (width, height) = calculate_logical_size(
(mode.width, mode.height),
self.global.persistent.transform.get(),
self.global.persistent.scale.get(),
);
let pos = self.global.pos.get();
pos.with_size(width, height).unwrap()
}
fn change_extents_(self: &Rc<Self>, rect: &Rect) {
self.global.persistent.pos.set((rect.x1(), rect.y1()));
self.global.pos.set(*rect);
self.state.root.update_extents();
self.schedule_update_render_data();
@ -766,3 +762,17 @@ impl Node for OutputNode {
true
}
}
pub fn calculate_logical_size(
mode: (i32, i32),
transform: Transform,
scale: crate::scale::Scale,
) -> (i32, i32) {
let (mut width, mut height) = transform.maybe_swap(mode);
if scale != 1 {
let scale = scale.to_f64();
width = (width as f64 / scale).round() as _;
height = (height as f64 / scale).round() as _;
}
(width, height)
}

22
toml-config/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "jay-toml-config"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib", "cdylib"]
[dependencies]
jay-config = { path = "../jay-config" }
log = "0.4.14"
thiserror = "1.0.57"
error_reporter = "1.0.0"
phf = { version = "0.11.2", features = ["macros"] }
indexmap = "2.2.5"
bstr = { version = "1.9.1", default-features = false }
ahash = "0.8.11"
[dev-dependencies]
simplelog = { version = "0.12.2", features = ["test"] }
serde_json = "1.0.114"
walkdir = "2.5.0"

307
toml-config/src/config.rs Normal file
View file

@ -0,0 +1,307 @@
mod context;
pub mod error;
mod extractor;
mod keysyms;
mod parser;
mod parsers;
mod spanned;
mod value;
use {
crate::{
config::{
context::Context,
parsers::config::{ConfigParser, ConfigParserError},
},
toml::{self},
},
jay_config::{
input::acceleration::AccelProfile,
keyboard::{Keymap, ModifiedKeySym},
logging::LogLevel,
status::MessageFormat,
theme::Color,
video::{GfxApi, Transform},
Axis, Direction,
},
std::{
error::Error,
fmt::{Display, Formatter},
time::Duration,
},
thiserror::Error,
toml::toml_parser,
};
#[derive(Debug, Copy, Clone)]
pub enum SimpleCommand {
Close,
DisablePointerConstraint,
Focus(Direction),
FocusParent,
Move(Direction),
None,
Quit,
ReloadConfigSo,
ReloadConfigToml,
Split(Axis),
ToggleFloating,
ToggleFullscreen,
ToggleMono,
ToggleSplit,
}
#[derive(Debug, Clone)]
pub enum Action {
ConfigureConnector { con: ConfigConnector },
ConfigureDirectScanout { enabled: bool },
ConfigureDrmDevice { dev: ConfigDrmDevice },
ConfigureIdle { idle: Duration },
ConfigureInput { input: Input },
ConfigureOutput { out: Output },
Exec { exec: Exec },
Multi { actions: Vec<Action> },
SetEnv { env: Vec<(String, String)> },
SetGfxApi { api: GfxApi },
SetKeymap { map: ConfigKeymap },
SetLogLevel { level: LogLevel },
SetRenderDevice { dev: DrmDeviceMatch },
SetStatus { status: Option<Status> },
SetTheme { theme: Box<Theme> },
ShowWorkspace { name: String },
SimpleCommand { cmd: SimpleCommand },
SwitchToVt { num: u32 },
UnsetEnv { env: Vec<String> },
}
#[derive(Debug, Clone, Default)]
pub struct Theme {
pub attention_requested_bg_color: Option<Color>,
pub bg_color: Option<Color>,
pub bar_bg_color: Option<Color>,
pub bar_status_text_color: Option<Color>,
pub border_color: Option<Color>,
pub captured_focused_title_bg_color: Option<Color>,
pub captured_unfocused_title_bg_color: Option<Color>,
pub focused_inactive_title_bg_color: Option<Color>,
pub focused_inactive_title_text_color: Option<Color>,
pub focused_title_bg_color: Option<Color>,
pub focused_title_text_color: Option<Color>,
pub separator_color: Option<Color>,
pub unfocused_title_bg_color: Option<Color>,
pub unfocused_title_text_color: Option<Color>,
pub border_width: Option<i32>,
pub title_height: Option<i32>,
pub font: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Status {
pub format: MessageFormat,
pub exec: Exec,
pub separator: Option<String>,
}
#[derive(Debug, Clone)]
pub enum OutputMatch {
Any(Vec<OutputMatch>),
All {
name: Option<String>,
connector: Option<String>,
serial_number: Option<String>,
manufacturer: Option<String>,
model: Option<String>,
},
}
#[derive(Debug, Clone)]
pub enum DrmDeviceMatch {
Any(Vec<DrmDeviceMatch>),
All {
name: Option<String>,
syspath: Option<String>,
vendor: Option<u32>,
vendor_name: Option<String>,
model: Option<u32>,
model_name: Option<String>,
devnode: Option<String>,
},
}
#[derive(Debug, Clone)]
pub struct Mode {
pub width: i32,
pub height: i32,
pub refresh_rate: Option<f64>,
}
impl Display for Mode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} x {}", self.width, self.height)?;
if let Some(rr) = self.refresh_rate {
write!(f, " @ {}", rr)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Output {
pub name: Option<String>,
pub match_: OutputMatch,
pub x: Option<i32>,
pub y: Option<i32>,
pub scale: Option<f64>,
pub transform: Option<Transform>,
pub mode: Option<Mode>,
}
#[derive(Debug, Clone)]
pub enum ConnectorMatch {
Any(Vec<ConnectorMatch>),
All { connector: Option<String> },
}
#[derive(Debug, Clone)]
pub enum InputMatch {
Any(Vec<InputMatch>),
All {
tag: Option<String>,
name: Option<String>,
syspath: Option<String>,
devnode: Option<String>,
is_keyboard: Option<bool>,
is_pointer: Option<bool>,
is_touch: Option<bool>,
is_tablet_tool: Option<bool>,
is_tablet_pad: Option<bool>,
is_gesture: Option<bool>,
is_switch: Option<bool>,
},
}
#[derive(Debug, Clone)]
pub struct Input {
pub tag: Option<String>,
pub match_: InputMatch,
pub accel_profile: Option<AccelProfile>,
pub accel_speed: Option<f64>,
pub tap_enabled: Option<bool>,
pub tap_drag_enabled: Option<bool>,
pub tap_drag_lock_enabled: Option<bool>,
pub left_handed: Option<bool>,
pub natural_scrolling: Option<bool>,
pub px_per_wheel_scroll: Option<f64>,
pub transform_matrix: Option<[[f64; 2]; 2]>,
}
#[derive(Debug, Clone)]
pub struct Exec {
pub prog: String,
pub args: Vec<String>,
pub envs: Vec<(String, String)>,
}
#[derive(Debug, Clone)]
pub struct ConfigConnector {
pub match_: ConnectorMatch,
pub enabled: bool,
}
#[derive(Debug, Clone)]
pub struct ConfigDrmDevice {
pub name: Option<String>,
pub match_: DrmDeviceMatch,
pub gfx_api: Option<GfxApi>,
pub direct_scanout_enabled: Option<bool>,
}
#[derive(Debug, Clone)]
pub enum ConfigKeymap {
Named(String),
Literal(Keymap),
Defined { name: String, map: Keymap },
}
#[derive(Debug, Clone)]
pub struct Config {
pub keymap: Option<ConfigKeymap>,
pub shortcuts: Vec<(ModifiedKeySym, Action)>,
pub on_graphics_initialized: Option<Action>,
pub on_idle: Option<Action>,
pub status: Option<Status>,
pub connectors: Vec<ConfigConnector>,
pub outputs: Vec<Output>,
pub workspace_capture: bool,
pub env: Vec<(String, String)>,
pub on_startup: Option<Action>,
pub keymaps: Vec<ConfigKeymap>,
pub log_level: Option<LogLevel>,
pub theme: Theme,
pub gfx_api: Option<GfxApi>,
pub direct_scanout_enabled: Option<bool>,
pub drm_devices: Vec<ConfigDrmDevice>,
pub render_device: Option<DrmDeviceMatch>,
pub inputs: Vec<Input>,
pub idle: Option<Duration>,
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Could not parse the toml document")]
Toml(#[from] toml_parser::ParserError),
#[error("Could not interpret the toml as a config document")]
Parser(#[from] ConfigParserError),
}
pub fn parse_config<F>(input: &[u8], handle_error: F) -> Option<Config>
where
F: FnOnce(&dyn Error),
{
let cx = Context {
input,
used: Default::default(),
};
macro_rules! fatal {
($e:expr) => {{
let e = ConfigError::from($e.value);
let e = cx.error2($e.span, e);
handle_error(&e);
return None;
}};
}
let toml = match toml_parser::parse(input, &cx) {
Ok(t) => t,
Err(e) => fatal!(e),
};
let config = match toml.parse(&mut ConfigParser(&cx)) {
Ok(c) => c,
Err(e) => fatal!(e),
};
let used = cx.used.take();
macro_rules! check_defined {
($name:expr, $used:ident, $defined:ident) => {
for spanned in &used.$used {
if !used.$defined.contains(spanned) {
log::warn!(
"{} {} used but not defined: {}",
$name,
spanned.value,
cx.error3(spanned.span),
);
}
}
};
}
check_defined!("Keymap", keymaps, defined_keymaps);
check_defined!("DRM device", drm_devices, defined_drm_devices);
check_defined!("Output", outputs, defined_outputs);
check_defined!("Input", inputs, defined_inputs);
Some(config)
}
#[test]
fn default_config_parses() {
let input = include_bytes!("default-config.toml");
parse_config(input, |_| ()).unwrap();
}

View file

@ -0,0 +1,65 @@
use {
crate::{
config::error::SpannedError,
toml::{
toml_parser::{ErrorHandler, ParserError},
toml_span::{Span, Spanned},
},
},
ahash::AHashSet,
error_reporter::Report,
std::{cell::RefCell, convert::Infallible, error::Error},
};
pub struct Context<'a> {
pub input: &'a [u8],
pub used: RefCell<Used>,
}
#[derive(Default)]
pub struct Used {
pub outputs: Vec<Spanned<String>>,
pub inputs: Vec<Spanned<String>>,
pub drm_devices: Vec<Spanned<String>>,
pub keymaps: Vec<Spanned<String>>,
pub defined_outputs: AHashSet<Spanned<String>>,
pub defined_inputs: AHashSet<Spanned<String>>,
pub defined_drm_devices: AHashSet<Spanned<String>>,
pub defined_keymaps: AHashSet<Spanned<String>>,
}
impl<'a> Context<'a> {
pub fn error<E: Error>(&self, cause: Spanned<E>) -> SpannedError<'a, E> {
self.error2(cause.span, cause.value)
}
pub fn error2<E: Error>(&self, span: Span, cause: E) -> SpannedError<'a, E> {
SpannedError {
input: self.input.into(),
span,
cause: Some(cause),
}
}
pub fn error3(&self, span: Span) -> SpannedError<'a, Infallible> {
SpannedError {
input: self.input.into(),
span,
cause: None,
}
}
}
impl<'a> ErrorHandler for Context<'a> {
fn handle(&self, err: Spanned<ParserError>) {
log::warn!("{}", Report::new(self.error(err)));
}
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
log::warn!("{}", Report::new(self.error(err)));
log::info!(
"Previous definition here: {}",
Report::new(self.error3(prev))
);
}
}

View file

@ -0,0 +1,89 @@
use {
crate::toml::toml_span::Span,
bstr::ByteSlice,
error_reporter::Report,
std::{
borrow::Cow,
error::Error,
fmt::{Display, Formatter},
ops::Deref,
},
};
#[derive(Debug)]
pub struct SpannedError<'a, E> {
pub input: Cow<'a, [u8]>,
pub span: Span,
pub cause: Option<E>,
}
impl<'a, E: Error> Display for SpannedError<'a, E> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let original = self.input.deref();
let span = self.span;
let (line, column) = translate_position(original, span.lo);
let line_num = line + 1;
let col_num = column + 1;
let gutter = line_num.to_string().len();
let content = original
.split(|c| *c == b'\n')
.nth(line)
.expect("valid line number");
if let Some(cause) = &self.cause {
write!(f, "{}: ", Report::new(cause))?;
}
writeln!(f, "At line {}, column {}:", line_num, col_num)?;
for _ in 0..=gutter {
write!(f, " ")?;
}
writeln!(f, "|")?;
write!(f, "{} | ", line_num)?;
writeln!(f, "{}", content.as_bstr())?;
for _ in 0..=gutter {
write!(f, " ")?;
}
write!(f, "|")?;
for _ in 0..=column {
write!(f, " ")?;
}
write!(f, "^")?;
for _ in (span.lo + 1)..(span.hi.min(span.lo + content.len() - column)) {
write!(f, "^")?;
}
Ok(())
}
}
impl<'a, E: Error> Error for SpannedError<'a, E> {}
fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
if input.is_empty() {
return (0, index);
}
let safe_index = index.min(input.len() - 1);
let column_offset = index - safe_index;
let index = safe_index;
let nl = input[0..index]
.iter()
.rev()
.enumerate()
.find(|(_, b)| **b == b'\n')
.map(|(nl, _)| index - nl - 1);
let line_start = match nl {
Some(nl) => nl + 1,
None => 0,
};
let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
let column = std::str::from_utf8(&input[line_start..=index])
.map(|s| s.chars().count() - 1)
.unwrap_or_else(|_| index - line_start);
let column = column + column_offset;
(line, column)
}

View file

@ -0,0 +1,281 @@
use {
crate::{
config::context::Context,
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
ahash::AHashSet,
error_reporter::Report,
indexmap::IndexMap,
thiserror::Error,
};
pub struct Extractor<'v> {
cx: &'v Context<'v>,
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
used: Vec<&'static str>,
log_unused: bool,
span: Span,
}
impl<'v> Extractor<'v> {
pub fn new(
cx: &'v Context<'v>,
span: Span,
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
) -> Self {
Self {
cx,
table,
used: Default::default(),
log_unused: true,
span,
}
}
fn get(&mut self, name: &'static str) -> Option<&'v Spanned<Value>> {
let v = self.table.get(name);
if v.is_some() {
self.used.push(name);
}
v
}
pub fn ignore_unused(&mut self) {
self.log_unused = false;
}
pub fn extract<E: Extractable<'v>, U>(&mut self, e: E) -> Result<E::Output, Spanned<U>>
where
ExtractorError: Into<U>,
{
e.extract(self).map_err(|e| e.map(|e| e.into()))
}
pub fn extract_or_ignore<E: Extractable<'v>, U>(
&mut self,
e: E,
) -> Result<E::Output, Spanned<U>>
where
ExtractorError: Into<U>,
{
let res = self.extract(e);
if res.is_err() {
self.ignore_unused();
}
res
}
}
impl<'v> Drop for Extractor<'v> {
fn drop(&mut self) {
if !self.log_unused {
return;
}
if self.used.len() == self.table.len() {
return;
}
let used: AHashSet<_> = self.used.iter().copied().collect();
for key in self.table.keys() {
if !used.contains(key.value.as_str()) {
#[derive(Debug, Error)]
#[error("Ignoring unknown key {0}")]
struct Err<'a>(&'a str);
let err = self.cx.error2(key.span, Err(&key.value));
log::warn!("{}", Report::new(err));
}
}
}
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum ExtractorError {
#[error("Missing field {0}")]
MissingField(&'static str),
#[error("Expected {0} but found {1}")]
Expected(&'static str, &'static str),
#[error("Value must fit in a u32")]
U32,
#[error("Value must fit in a u64")]
U64,
#[error("Value must fit in a i32")]
I32,
}
pub trait Extractable<'v> {
type Output;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>>;
}
impl<'v, T, F> Extractable<'v> for F
where
F: FnOnce(&mut Extractor<'v>) -> Result<T, Spanned<ExtractorError>>,
{
type Output = T;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>> {
self(extractor)
}
}
pub fn val(
name: &'static str,
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<&'v Value>, Spanned<ExtractorError>>
{
move |extractor: &mut Extractor| match extractor.get(name) {
None => Err(ExtractorError::MissingField(name).spanned(extractor.span)),
Some(v) => Ok(v.as_ref()),
}
}
macro_rules! ty {
($f:ident, $lt:lifetime, $ty:ident, $ret:ty, $v:ident, $map:expr, $name:expr) => {
#[allow(dead_code)]
pub fn $f(
name: &'static str,
) -> impl for<$lt> FnOnce(&mut Extractor<$lt>) -> Result<Spanned<$ret>, Spanned<ExtractorError>> {
move |extractor: &mut Extractor| {
val(name)(extractor).and_then(|v| match v.value {
Value::$ty($v) => Ok($map.spanned(v.span)),
_ => Err(ExtractorError::Expected($name, v.value.name()).spanned(v.span)),
})
}
}
};
}
ty!(str, 'a, String, &'a str, v, v.as_str(), "a string");
ty!(int, 'a, Integer, i64, v, *v, "an integer");
ty!(flt, 'a, Float, f64, v, *v, "a float");
ty!(bol, 'a, Boolean, bool, v, *v, "a boolean");
ty!(arr, 'a, Array, &'a [Spanned<Value>], v, &**v, "an array");
ty!(tbl, 'a, Table, &'a IndexMap<Spanned<String>, Spanned<Value>>, v, v, "a table");
pub fn fltorint(
name: &'static str,
) -> impl for<'a> FnOnce(&mut Extractor<'a>) -> Result<Spanned<f64>, Spanned<ExtractorError>> {
move |extractor: &mut Extractor| {
val(name)(extractor).and_then(|v| match *v.value {
Value::Float(f) => Ok(f.spanned(v.span)),
Value::Integer(i) => Ok((i as f64).spanned(v.span)),
_ => Err(
ExtractorError::Expected("a float or an integer", v.value.name()).spanned(v.span),
),
})
}
}
macro_rules! int {
($f:ident, $ty:ident, $err:ident) => {
#[allow(dead_code)]
pub fn $f(
name: &'static str,
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<$ty>, Spanned<ExtractorError>> {
move |extractor: &mut Extractor| {
int(name)(extractor).and_then(|v| {
v.value
.try_into()
.map(|n: $ty| n.spanned(v.span))
.map_err(|_| ExtractorError::$err.spanned(v.span))
})
}
}
};
}
int!(n32, u32, U32);
int!(n64, u64, U64);
int!(s32, i32, I32);
pub fn recover<F>(f: F) -> Recover<F> {
Recover(f)
}
pub struct Recover<E>(E);
impl<'v, E> Extractable<'v> for Recover<E>
where
E: Extractable<'v>,
<E as Extractable<'v>>::Output: Default,
{
type Output = <E as Extractable<'v>>::Output;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>> {
match self.0.extract(extractor) {
Ok(v) => Ok(v),
Err(e) => {
log::warn!("{}", extractor.cx.error(e));
Ok(<E as Extractable<'v>>::Output::default())
}
}
}
}
pub fn opt<F>(f: F) -> Opt<F> {
Opt(f)
}
pub struct Opt<E>(E);
impl<'v, E> Extractable<'v> for Opt<E>
where
E: Extractable<'v>,
{
type Output = Option<<E as Extractable<'v>>::Output>;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>> {
match self.0.extract(extractor) {
Ok(v) => Ok(Some(v)),
Err(e) if matches!(e.value, ExtractorError::MissingField(_)) => Ok(None),
Err(e) => Err(e),
}
}
}
macro_rules! tuples {
($($idx:tt: $name:ident,)*) => {
impl<'v, $($name,)*> Extractable<'v> for ($($name,)*)
where $($name: Extractable<'v>,)*
{
type Output = ($($name::Output,)*);
#[allow(non_snake_case)]
fn extract(self, extractor: &mut Extractor<'v>) -> Result<Self::Output, Spanned<ExtractorError>> {
$(
let $name = self.$idx.extract(extractor);
)*
Ok((
$(
$name?,
)*
))
}
}
};
}
tuples!(0:T0,);
tuples!(0:T0,1:T1,);
tuples!(0:T0,1:T1,2:T2,);
tuples!(0:T0,1:T1,2:T2,3:T3,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9,);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,118 @@
use {
crate::toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
indexmap::IndexMap,
std::{
error::Error,
fmt::{self, Display, Formatter},
},
};
#[derive(Copy, Clone, Debug)]
pub enum DataType {
String,
Integer,
Float,
Boolean,
Array,
Table,
}
impl Display for DataType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let s = match self {
DataType::String => "a string",
DataType::Integer => "an integer",
DataType::Float => "a float",
DataType::Boolean => "a bool",
DataType::Array => "an array",
DataType::Table => "a table",
};
f.write_str(s)
}
}
pub struct DataTypes(&'static [DataType]);
impl Display for DataTypes {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let d = self.0;
match d.len() {
0 => Ok(()),
1 => d[0].fmt(f),
2 => write!(f, "{} or {}", d[0], d[1]),
_ => {
let mut first = true;
#[allow(clippy::needless_range_loop)]
for i in 0..d.len() - 1 {
if !first {
f.write_str(", ")?;
}
first = false;
d[i].fmt(f)?;
}
write!(f, ", or {}", d[d.len() - 1])
}
}
}
}
#[derive(Debug)]
pub struct UnexpectedDataType(&'static [DataType], DataType);
impl Display for UnexpectedDataType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Expected {} but found {}", DataTypes(self.0), self.1)
}
}
impl Error for UnexpectedDataType {}
pub type ParseResult<P> = Result<<P as Parser>::Value, Spanned<<P as Parser>::Error>>;
pub trait Parser {
type Value;
type Error: From<UnexpectedDataType>;
const EXPECTED: &'static [DataType];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let _ = string;
expected(self, span, DataType::String)
}
fn parse_integer(&mut self, span: Span, integer: i64) -> ParseResult<Self> {
let _ = integer;
expected(self, span, DataType::Integer)
}
fn parse_float(&mut self, span: Span, float: f64) -> ParseResult<Self> {
let _ = float;
expected(self, span, DataType::Float)
}
fn parse_bool(&mut self, span: Span, bool: bool) -> ParseResult<Self> {
let _ = bool;
expected(self, span, DataType::Boolean)
}
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let _ = array;
expected(self, span, DataType::Array)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let _ = table;
expected(self, span, DataType::Table)
}
}
fn expected<P: Parser + ?Sized>(_p: &P, span: Span, actual: DataType) -> ParseResult<P> {
Err(P::Error::from(UnexpectedDataType(P::EXPECTED, actual)).spanned(span))
}

View file

@ -0,0 +1,48 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
toml::toml_span::Span,
},
thiserror::Error,
};
pub mod action;
mod color;
pub mod config;
mod connector;
mod connector_match;
mod drm_device;
mod drm_device_match;
mod env;
pub mod exec;
mod gfx_api;
mod idle;
mod input;
mod input_match;
pub mod keymap;
mod log_level;
mod mode;
pub mod modified_keysym;
mod output;
mod output_match;
pub mod shortcuts;
mod status;
mod theme;
#[derive(Debug, Error)]
pub enum StringParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
}
pub struct StringParser;
impl Parser for StringParser {
type Value = String;
type Error = StringParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
Ok(string.to_string())
}
}

View file

@ -0,0 +1,308 @@
use {
crate::{
config::{
context::Context,
extractor::{arr, bol, n32, opt, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
connector::{ConnectorParser, ConnectorParserError},
drm_device::{DrmDeviceParser, DrmDeviceParserError},
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
env::{EnvParser, EnvParserError},
exec::{ExecParser, ExecParserError},
gfx_api::{GfxApiParser, GfxApiParserError},
idle::{IdleParser, IdleParserError},
input::{InputParser, InputParserError},
keymap::{KeymapParser, KeymapParserError},
log_level::{LogLevelParser, LogLevelParserError},
output::{OutputParser, OutputParserError},
status::{StatusParser, StatusParserError},
theme::{ThemeParser, ThemeParserError},
StringParser, StringParserError,
},
Action,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::Axis::{Horizontal, Vertical},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ActionParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
StringParser(#[from] StringParserError),
#[error("Unknown type {0}")]
UnknownType(String),
#[error("Unknown simple action {0}")]
UnknownSimpleAction(String),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("Could not parse the exec action")]
Exec(#[from] ExecParserError),
#[error("Could not parse the configure-connector action")]
ConfigureConnector(#[from] ConnectorParserError),
#[error("Could not parse the configure-input action")]
ConfigureInput(#[from] InputParserError),
#[error("Could not parse the configure-output action")]
ConfigureOutput(#[from] OutputParserError),
#[error("Could not parse the environment variables")]
Env(#[from] EnvParserError),
#[error("Could not parse a set-keymap action")]
SetKeymap(#[from] KeymapParserError),
#[error("Could not parse a set-status action")]
Status(#[from] StatusParserError),
#[error("Could not parse a set-theme action")]
Theme(#[from] ThemeParserError),
#[error("Could not parse a set-log-level action")]
SetLogLevel(#[from] LogLevelParserError),
#[error("Could not parse a set-gfx-api action")]
GfxApi(#[from] GfxApiParserError),
#[error("Could not parse a configure-drm-device action")]
DrmDevice(#[from] DrmDeviceParserError),
#[error("Could not parse a set-render-device action")]
SetRenderDevice(#[from] DrmDeviceMatchParserError),
#[error("Could not parse a configure-idle action")]
ConfigureIdle(#[from] IdleParserError),
}
pub struct ActionParser<'a>(pub &'a Context<'a>);
impl ActionParser<'_> {
fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult<Self> {
use {crate::config::SimpleCommand::*, jay_config::Direction::*};
let cmd = match string {
"focus-left" => Focus(Left),
"focus-down" => Focus(Down),
"focus-up" => Focus(Up),
"focus-right" => Focus(Right),
"move-left" => Move(Left),
"move-down" => Move(Down),
"move-up" => Move(Up),
"move-right" => Move(Right),
"split-horizontal" => Split(Horizontal),
"split-vertical" => Split(Vertical),
"toggle-split" => ToggleSplit,
"toggle-mono" => ToggleMono,
"toggle-fullscreen" => ToggleFullscreen,
"focus-parent" => FocusParent,
"close" => Close,
"disable-pointer-constraint" => DisablePointerConstraint,
"toggle-floating" => ToggleFloating,
"quit" => Quit,
"reload-config-toml" => ReloadConfigToml,
"reload-config-so" => ReloadConfigSo,
"none" => None,
_ => {
return Err(ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span))
}
};
Ok(Action::SimpleCommand { cmd })
}
fn parse_multi(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut actions = vec![];
for el in array {
actions.push(el.parse(self)?);
}
Ok(Action::Multi { actions })
}
fn parse_exec(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let exec = ext
.extract(val("exec"))?
.parse_map(&mut ExecParser(self.0))?;
Ok(Action::Exec { exec })
}
fn parse_switch_to_vt(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let num = ext.extract(n32("num"))?.value;
Ok(Action::SwitchToVt { num })
}
fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let name = ext.extract(str("name"))?.value.to_string();
Ok(Action::ShowWorkspace { name })
}
fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let name = ext.extract(str("name"))?.value.to_string();
Ok(Action::ShowWorkspace { name })
}
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let con = ext
.extract(val("connector"))?
.parse_map(&mut ConnectorParser(self.0))?;
Ok(Action::ConfigureConnector { con })
}
fn parse_configure_input(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let input = ext.extract(val("input"))?.parse_map(&mut InputParser {
cx: self.0,
tag_ok: false,
})?;
Ok(Action::ConfigureInput { input })
}
fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let idle = ext
.extract(val("idle"))?
.parse_map(&mut IdleParser(self.0))?;
Ok(Action::ConfigureIdle { idle })
}
fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let out = ext.extract(val("output"))?.parse_map(&mut OutputParser {
cx: self.0,
name_ok: false,
})?;
Ok(Action::ConfigureOutput { out })
}
fn parse_set_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let env = ext.extract(val("env"))?.parse_map(&mut EnvParser)?;
Ok(Action::SetEnv { env })
}
fn parse_unset_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
struct P;
impl Parser for P {
type Value = Vec<String>;
type Error = ActionParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
Ok(vec![string.to_string()])
}
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for v in array {
res.push(v.parse_map(&mut StringParser)?);
}
Ok(res)
}
}
let env = ext.extract(val("env"))?.parse_map(&mut P)?;
Ok(Action::UnsetEnv { env })
}
fn parse_set_keymap(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let map = ext.extract(val("map"))?.parse_map(&mut KeymapParser {
cx: self.0,
definition: false,
})?;
Ok(Action::SetKeymap { map })
}
fn parse_set_status(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let status = match ext.extract(opt(val("status")))? {
None => None,
Some(v) => Some(v.parse_map(&mut StatusParser(self.0))?),
};
Ok(Action::SetStatus { status })
}
fn parse_set_theme(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let theme = ext
.extract(val("theme"))?
.parse_map(&mut ThemeParser(self.0))?;
Ok(Action::SetTheme {
theme: Box::new(theme),
})
}
fn parse_set_log_level(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let level = ext.extract(val("level"))?.parse_map(&mut LogLevelParser)?;
Ok(Action::SetLogLevel { level })
}
fn parse_set_gfx_api(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let api = ext.extract(val("api"))?.parse_map(&mut GfxApiParser)?;
Ok(Action::SetGfxApi { api })
}
fn parse_set_render_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let dev = ext
.extract(val("dev"))?
.parse_map(&mut DrmDeviceMatchParser(self.0))?;
Ok(Action::SetRenderDevice { dev })
}
fn parse_configure_direct_scanout(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let enabled = ext.extract(bol("enabled"))?.value;
Ok(Action::ConfigureDirectScanout { enabled })
}
fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let dev = ext.extract(val("dev"))?.parse_map(&mut DrmDeviceParser {
cx: self.0,
name_ok: false,
})?;
Ok(Action::ConfigureDrmDevice { dev })
}
}
impl<'a> Parser for ActionParser<'a> {
type Value = Action;
type Error = ActionParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
self.parse_simple_cmd(span, string)
}
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
self.parse_multi(span, array)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let ty = ext.extract_or_ignore(str("type"))?;
let res = match ty.value {
"simple" => {
let cmd = ext.extract(str("cmd"))?;
self.parse_simple_cmd(cmd.span, cmd.value)
}
"multi" => {
let actions = ext.extract(arr("actions"))?;
self.parse_multi(actions.span, actions.value)
}
"exec" => self.parse_exec(&mut ext),
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
"show-workspace" => self.parse_show_workspace(&mut ext),
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
"configure-connector" => self.parse_configure_connector(&mut ext),
"configure-input" => self.parse_configure_input(&mut ext),
"configure-output" => self.parse_configure_output(&mut ext),
"set-env" => self.parse_set_env(&mut ext),
"unset-env" => self.parse_unset_env(&mut ext),
"set-keymap" => self.parse_set_keymap(&mut ext),
"set-status" => self.parse_set_status(&mut ext),
"set-theme" => self.parse_set_theme(&mut ext),
"set-log-level" => self.parse_set_log_level(&mut ext),
"set-gfx-api" => self.parse_set_gfx_api(&mut ext),
"configure-direct-scanout" => self.parse_configure_direct_scanout(&mut ext),
"configure-drm-device" => self.parse_configure_drm_device(&mut ext),
"set-render-device" => self.parse_set_render_device(&mut ext),
"configure-idle" => self.parse_configure_idle(&mut ext),
v => {
ext.ignore_unused();
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
}
};
drop(ext);
res
}
}

View file

@ -0,0 +1,58 @@
use {
crate::{
config::{
context::Context,
extractor::ExtractorError,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::toml_span::{Span, SpannedExt},
},
jay_config::theme::Color,
std::{num::ParseIntError, ops::Range},
thiserror::Error,
};
pub struct ColorParser<'a>(pub &'a Context<'a>);
#[derive(Debug, Error)]
pub enum ColorParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error("Color must start with `#`")]
Prefix,
#[error("String must have length 4, 5, 6, or 9")]
Length,
#[error(transparent)]
ParseIntError(#[from] ParseIntError),
}
impl Parser for ColorParser<'_> {
type Value = Color;
type Error = ColorParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let hex = match string.strip_prefix("#") {
Some(s) => s,
_ => return Err(ColorParserError::Prefix.spanned(span)),
};
let d = |range: Range<usize>| {
u8::from_str_radix(&hex[range], 16)
.map_err(|e| ColorParserError::ParseIntError(e).spanned(span))
};
let s = |range: Range<usize>| {
let v = d(range)?;
Ok((v << 4) | v)
};
let (r, g, b, a) = match hex.len() {
3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX),
4 => (s(0..1)?, s(1..2)?, s(2..3)?, s(3..4)?),
6 => (d(0..2)?, d(2..4)?, d(4..6)?, u8::MAX),
8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(4..8)?),
_ => return Err(ColorParserError::Length.spanned(span)),
};
Ok(Color::new_straight(r, g, b, a))
}
}

View file

@ -0,0 +1,279 @@
use {
crate::{
config::{
context::Context,
extractor::{arr, bol, opt, recover, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::ActionParser,
connector::ConnectorsParser,
drm_device::DrmDevicesParser,
drm_device_match::DrmDeviceMatchParser,
env::EnvParser,
gfx_api::GfxApiParser,
idle::IdleParser,
input::InputsParser,
keymap::KeymapParser,
log_level::LogLevelParser,
output::OutputsParser,
shortcuts::{ShortcutsParser, ShortcutsParserError},
status::StatusParser,
theme::ThemeParser,
},
spanned::SpannedErrorExt,
Action, Config, Theme,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ConfigParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error("Could not parse the shortcuts")]
ParseShortcuts(#[source] ShortcutsParserError),
}
pub struct ConfigParser<'a>(pub &'a Context<'a>);
impl ConfigParser<'_> {
fn parse_action(&self, name: &str, action: Option<Spanned<&Value>>) -> Option<Action> {
match action {
None => None,
Some(value) => match value.parse(&mut ActionParser(self.0)) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse the {name} action: {}", self.0.error(e));
None
}
},
}
}
}
impl Parser for ConfigParser<'_> {
type Value = Config;
type Error = ConfigParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (
(
keymap_val,
shortcuts_val,
on_graphics_init_val,
status_val,
outputs_val,
connectors_val,
workspace_capture,
env_val,
on_startup_val,
keymaps_val,
),
(
log_level_val,
theme_val,
gfx_api_val,
drm_devices_val,
direct_scanout,
render_device_val,
inputs_val,
on_idle_val,
_,
idle_val,
),
) = ext.extract((
(
opt(val("keymap")),
opt(val("shortcuts")),
opt(val("on-graphics-initialized")),
opt(val("status")),
opt(val("outputs")),
opt(val("connectors")),
recover(opt(bol("workspace-capture"))),
opt(val("env")),
opt(val("on-startup")),
recover(opt(arr("keymaps"))),
),
(
opt(val("log-level")),
opt(val("theme")),
opt(val("gfx-api")),
opt(val("drm-devices")),
recover(opt(bol("direct-scanout"))),
opt(val("render-device")),
opt(val("inputs")),
opt(val("on-idle")),
opt(val("$schema")),
opt(val("idle")),
),
))?;
let mut keymap = None;
if let Some(value) = keymap_val {
match value.parse(&mut KeymapParser {
cx: self.0,
definition: false,
}) {
Ok(m) => keymap = Some(m),
Err(e) => {
log::warn!("Could not parse the keymap: {}", self.0.error(e));
}
}
}
let mut shortcuts = vec![];
if let Some(value) = shortcuts_val {
shortcuts = value
.parse(&mut ShortcutsParser(self.0))
.map_spanned_err(ConfigParserError::ParseShortcuts)?;
}
if shortcuts.is_empty() {
log::warn!("Config defines no shortcuts");
}
let on_graphics_initialized =
self.parse_action("on-graphics-initialized", on_graphics_init_val);
let on_idle = self.parse_action("on-idle", on_idle_val);
let on_startup = self.parse_action("on-startup", on_startup_val);
let mut status = None;
if let Some(value) = status_val {
match value.parse(&mut StatusParser(self.0)) {
Ok(v) => status = Some(v),
Err(e) => log::warn!("Could not parse the status config: {}", self.0.error(e)),
}
}
let mut outputs = vec![];
if let Some(value) = outputs_val {
match value.parse(&mut OutputsParser(self.0)) {
Ok(v) => outputs = v,
Err(e) => log::warn!("Could not parse the outputs: {}", self.0.error(e)),
}
}
let mut connectors = vec![];
if let Some(value) = connectors_val {
match value.parse(&mut ConnectorsParser(self.0)) {
Ok(v) => connectors = v,
Err(e) => log::warn!("Could not parse the connectors: {}", self.0.error(e)),
}
}
let mut env = vec![];
if let Some(value) = env_val {
match value.parse(&mut EnvParser) {
Ok(v) => env = v,
Err(e) => log::warn!(
"Could not parse the environment variables: {}",
self.0.error(e)
),
}
}
let mut keymaps = vec![];
if let Some(value) = keymaps_val {
for value in value.value {
match value.parse(&mut KeymapParser {
cx: self.0,
definition: true,
}) {
Ok(m) => keymaps.push(m),
Err(e) => {
log::warn!("Could not parse a keymap: {}", self.0.error(e));
}
}
}
}
let mut log_level = None;
if let Some(value) = log_level_val {
match value.parse(&mut LogLevelParser) {
Ok(v) => log_level = Some(v),
Err(e) => {
log::warn!("Could not parse the log level: {}", self.0.error(e));
}
}
}
let mut theme = Theme::default();
if let Some(value) = theme_val {
match value.parse(&mut ThemeParser(self.0)) {
Ok(v) => theme = v,
Err(e) => {
log::warn!("Could not parse the theme: {}", self.0.error(e));
}
}
}
let mut gfx_api = None;
if let Some(value) = gfx_api_val {
match value.parse(&mut GfxApiParser) {
Ok(v) => gfx_api = Some(v),
Err(e) => {
log::warn!("Could not parse the graphics API: {}", self.0.error(e));
}
}
}
let mut drm_devices = vec![];
if let Some(value) = drm_devices_val {
match value.parse(&mut DrmDevicesParser(self.0)) {
Ok(v) => drm_devices = v,
Err(e) => {
log::warn!("Could not parse the drm devices: {}", self.0.error(e));
}
}
}
let mut render_device = None;
if let Some(value) = render_device_val {
match value.parse(&mut DrmDeviceMatchParser(self.0)) {
Ok(v) => render_device = Some(v),
Err(e) => {
log::warn!("Could not parse the render device: {}", self.0.error(e));
}
}
}
let mut inputs = vec![];
if let Some(value) = inputs_val {
match value.parse(&mut InputsParser(self.0)) {
Ok(v) => inputs = v,
Err(e) => {
log::warn!("Could not parse the inputs: {}", self.0.error(e));
}
}
}
let mut idle = None;
if let Some(value) = idle_val {
match value.parse(&mut IdleParser(self.0)) {
Ok(v) => idle = Some(v),
Err(e) => {
log::warn!("Could not parse the idle timeout: {}", self.0.error(e));
}
}
}
Ok(Config {
keymap,
shortcuts,
on_graphics_initialized,
on_idle,
status,
outputs,
connectors,
workspace_capture: workspace_capture.despan().unwrap_or(true),
env,
on_startup,
keymaps,
log_level,
theme,
gfx_api,
drm_devices,
direct_scanout_enabled: direct_scanout.despan(),
render_device,
inputs,
idle,
})
}
}

View file

@ -0,0 +1,83 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, opt, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::connector_match::{ConnectorMatchParser, ConnectorMatchParserError},
ConfigConnector,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ConnectorParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] ConnectorMatchParserError),
}
pub struct ConnectorParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ConnectorParser<'a> {
type Value = ConfigConnector;
type Error = ConnectorParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (match_val, enabled) = ext.extract((val("match"), opt(bol("enabled"))))?;
Ok(ConfigConnector {
match_: match_val.parse_map(&mut ConnectorMatchParser(self.0))?,
enabled: enabled.despan().unwrap_or(true),
})
}
}
pub struct ConnectorsParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ConnectorsParser<'a> {
type Value = Vec<ConfigConnector>;
type Error = ConnectorParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut ConnectorParser(self.0)) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse connector: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`connectors` value should be an array: {}",
self.0.error3(span)
);
ConnectorParser(self.0)
.parse_table(span, table)
.map(|v| vec![v])
}
}

View file

@ -0,0 +1,57 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
ConnectorMatch,
},
toml::{
toml_span::{Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ConnectorMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct ConnectorMatchParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ConnectorMatchParser<'a> {
type Value = ConnectorMatch;
type Error = ConnectorMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(ConnectorMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (connector,) = ext.extract((opt(str("name")),))?;
Ok(ConnectorMatch::All {
connector: connector.map(|v| v.value.to_owned()),
})
}
}

View file

@ -0,0 +1,126 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
gfx_api::GfxApiParser,
},
ConfigDrmDevice,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum DrmDeviceParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] DrmDeviceMatchParserError),
}
pub struct DrmDeviceParser<'a> {
pub cx: &'a Context<'a>,
pub name_ok: bool,
}
impl<'a> Parser for DrmDeviceParser<'a> {
type Value = ConfigDrmDevice;
type Error = DrmDeviceParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (name, match_val, direct_scanout_enabled, gfx_api_val) = ext.extract((
opt(str("name")),
val("match"),
recover(opt(bol("direct-scanout"))),
opt(val("gfx-api")),
))?;
let gfx_api = match gfx_api_val {
Some(api) => match api.parse(&mut GfxApiParser) {
Ok(m) => Some(m),
Err(e) => {
log::warn!("Could not parse graphics API: {}", self.cx.error(e));
None
}
},
None => None,
};
if let Some(name) = name {
if self.name_ok {
self.cx
.used
.borrow_mut()
.defined_drm_devices
.insert(name.into());
} else {
log::warn!(
"DRM device names have no effect in this position (did you mean match.name?): {}",
self.cx.error3(name.span)
);
}
}
Ok(ConfigDrmDevice {
name: name.despan().map(|v| v.to_string()),
match_: match_val.parse_map(&mut DrmDeviceMatchParser(self.cx))?,
direct_scanout_enabled: direct_scanout_enabled.despan(),
gfx_api,
})
}
}
pub struct DrmDevicesParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for DrmDevicesParser<'a> {
type Value = Vec<ConfigDrmDevice>;
type Error = DrmDeviceParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut DrmDeviceParser {
cx: self.0,
name_ok: true,
}) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse drm device: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`drm-devices` value should be an array: {}",
self.0.error3(span)
);
DrmDeviceParser {
cx: self.0,
name_ok: true,
}
.parse_table(span, table)
.map(|v| vec![v])
}
}

View file

@ -0,0 +1,74 @@
use {
crate::{
config::{
context::Context,
extractor::{n32, opt, recover, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
DrmDeviceMatch,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum DrmDeviceMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct DrmDeviceMatchParser<'a>(pub &'a Context<'a>);
impl Parser for DrmDeviceMatchParser<'_> {
type Value = DrmDeviceMatch;
type Error = DrmDeviceMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(DrmDeviceMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (name, syspath, vendor, vendor_name, model, model_name, devnode) = ext.extract((
recover(opt(str("name"))),
recover(opt(str("syspath"))),
recover(opt(n32("pci-vendor"))),
recover(opt(str("vendor"))),
recover(opt(n32("pci-model"))),
recover(opt(str("model"))),
recover(opt(str("devnode"))),
))?;
if let Some(name) = name {
self.0.used.borrow_mut().drm_devices.push(name.into());
}
Ok(DrmDeviceMatch::All {
name: name.despan_into(),
syspath: syspath.despan_into(),
vendor: vendor.despan(),
vendor_name: vendor_name.despan_into(),
model: model.despan(),
model_name: model_name.despan_into(),
devnode: devnode.despan_into(),
})
}
}

View file

@ -0,0 +1,42 @@
use {
crate::{
config::{
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{StringParser, StringParserError},
},
toml::{
toml_span::{Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum EnvParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
String(#[from] StringParserError),
}
pub struct EnvParser;
impl Parser for EnvParser {
type Value = Vec<(String, String)>;
type Error = EnvParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut envs = vec![];
for (k, v) in table {
envs.push((k.value.to_string(), v.parse_map(&mut StringParser)?));
}
Ok(envs)
}
}

View file

@ -0,0 +1,91 @@
use {
crate::{
config::{
context::Context,
extractor::{arr, opt, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
env::{EnvParser, EnvParserError},
StringParser, StringParserError,
},
Exec,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ExecParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error(transparent)]
String(#[from] StringParserError),
#[error(transparent)]
Env(#[from] EnvParserError),
#[error("Array cannot be empty")]
Empty,
}
pub struct ExecParser<'a>(pub &'a Context<'a>);
impl Parser for ExecParser<'_> {
type Value = Exec;
type Error = ExecParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
Ok(Exec {
prog: string.to_string(),
args: vec![],
envs: vec![],
})
}
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.is_empty() {
return Err(ExecParserError::Empty.spanned(span));
}
let prog = array[0].parse_map(&mut StringParser)?;
let mut args = vec![];
for v in &array[1..] {
args.push(v.parse_map(&mut StringParser)?);
}
Ok(Exec {
prog,
args,
envs: vec![],
})
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (prog, args_val, envs_val) =
ext.extract((str("prog"), opt(arr("args")), opt(val("env"))))?;
let mut args = vec![];
if let Some(args_val) = args_val {
for arg in args_val.value {
args.push(arg.parse_map(&mut StringParser)?);
}
}
let envs = match envs_val {
None => vec![],
Some(e) => e.parse_map(&mut EnvParser)?,
};
Ok(Exec {
prog: prog.value.to_string(),
args,
envs,
})
}
}

View file

@ -0,0 +1,34 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
toml::toml_span::{Span, SpannedExt},
},
jay_config::video::GfxApi,
thiserror::Error,
};
pub struct GfxApiParser;
#[derive(Debug, Error)]
pub enum GfxApiParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error("Unknown API {0}")]
Unknown(String),
}
impl Parser for GfxApiParser {
type Value = GfxApi;
type Error = GfxApiParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
use GfxApi::*;
let api = match string.to_ascii_lowercase().as_str() {
"opengl" => OpenGl,
"vulkan" => Vulkan,
_ => return Err(GfxApiParserError::Unknown(string.to_string()).spanned(span)),
};
Ok(api)
}
}

View file

@ -0,0 +1,45 @@
use {
crate::{
config::{
context::Context,
extractor::{n64, opt, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
std::time::Duration,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum IdleParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct IdleParser<'a>(pub &'a Context<'a>);
impl Parser for IdleParser<'_> {
type Value = Duration;
type Error = IdleParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (minutes, seconds) = ext.extract((opt(n64("minutes")), opt(n64("seconds"))))?;
let idle = Duration::from_secs(
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
);
Ok(idle)
}
}

View file

@ -0,0 +1,205 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, fltorint, opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::input_match::{InputMatchParser, InputMatchParserError},
Input,
},
toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::input::acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum InputParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] InputMatchParserError),
#[error("Transform matrix must have exactly two rows")]
TwoRows,
#[error("Transform matrix must have exactly two columns")]
TwoColumns,
#[error("Transform matrix entries must be floats")]
Float,
}
pub struct InputParser<'a> {
pub cx: &'a Context<'a>,
pub tag_ok: bool,
}
impl<'a> Parser for InputParser<'a> {
type Value = Input;
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (
(
tag,
match_val,
accel_profile,
accel_speed,
tap_enabled,
tap_drag_enabled,
tap_drag_lock_enabled,
left_handed,
natural_scrolling,
px_per_wheel_scroll,
),
(transform_matrix,),
) = ext.extract((
(
opt(str("tag")),
val("match"),
recover(opt(str("accel-profile"))),
recover(opt(fltorint("accel-speed"))),
recover(opt(bol("tap-enabled"))),
recover(opt(bol("tap-drag-enabled"))),
recover(opt(bol("tap-drag-lock-enabled"))),
recover(opt(bol("left-handed"))),
recover(opt(bol("natural-scrolling"))),
recover(opt(fltorint("px-per-wheel-scroll"))),
),
(recover(opt(val("transform-matrix"))),),
))?;
let accel_profile = match accel_profile {
None => None,
Some(p) => match p.value.to_ascii_lowercase().as_str() {
"flat" => Some(ACCEL_PROFILE_FLAT),
"adaptive" => Some(ACCEL_PROFILE_ADAPTIVE),
v => {
log::warn!("Unknown accel-profile {v}: {}", self.cx.error3(p.span));
None
}
},
};
let transform_matrix = match transform_matrix {
None => None,
Some(matrix) => match matrix.parse(&mut TransformMatrixParser) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse transform matrix: {}", self.cx.error(e));
None
}
},
};
if let Some(tag) = tag {
if self.tag_ok {
self.cx.used.borrow_mut().defined_inputs.insert(tag.into());
} else {
log::warn!(
"Input tags have no effect in this position (did you mean match.tag?): {}",
self.cx.error3(tag.span)
);
}
}
Ok(Input {
tag: tag.despan_into(),
match_: match_val.parse_map(&mut InputMatchParser(self.cx))?,
accel_profile,
accel_speed: accel_speed.despan(),
tap_enabled: tap_enabled.despan(),
tap_drag_enabled: tap_drag_enabled.despan(),
tap_drag_lock_enabled: tap_drag_lock_enabled.despan(),
left_handed: left_handed.despan(),
natural_scrolling: natural_scrolling.despan(),
px_per_wheel_scroll: px_per_wheel_scroll.despan(),
transform_matrix,
})
}
}
pub struct InputsParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for InputsParser<'a> {
type Value = Vec<Input>;
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut InputParser {
cx: self.0,
tag_ok: true,
}) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse output: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`outputs` value should be an array: {}",
self.0.error3(span)
);
InputParser {
cx: self.0,
tag_ok: true,
}
.parse_table(span, table)
.map(|v| vec![v])
}
}
struct TransformMatrixParser;
impl Parser for TransformMatrixParser {
type Value = [[f64; 2]; 2];
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array];
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.len() != 2 {
return Err(InputParserError::TwoRows.spanned(span));
}
Ok([
array[0].parse(&mut TransformMatrixRowParser)?,
array[1].parse(&mut TransformMatrixRowParser)?,
])
}
}
struct TransformMatrixRowParser;
impl Parser for TransformMatrixRowParser {
type Value = [f64; 2];
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array];
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.len() != 2 {
return Err(InputParserError::TwoColumns.spanned(span));
}
let extract = |v: &Spanned<Value>| match v.value {
Value::Float(f) => Ok(f),
Value::Integer(f) => Ok(f as _),
_ => Err(InputParserError::Float.spanned(v.span)),
};
Ok([extract(&array[0])?, extract(&array[1])?])
}
}

View file

@ -0,0 +1,98 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
InputMatch,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum InputMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct InputMatchParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for InputMatchParser<'a> {
type Value = InputMatch;
type Error = InputMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(InputMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (
(
tag,
name,
syspath,
devnode,
is_keyboard,
is_pointer,
is_touch,
is_tablet_tool,
is_tablet_pad,
is_gesture,
),
(is_switch,),
) = ext.extract((
(
opt(str("tag")),
opt(str("name")),
opt(str("syspath")),
opt(str("devnode")),
opt(bol("is-keyboard")),
opt(bol("is-pointer")),
opt(bol("is-touch")),
opt(bol("is-tablet-tool")),
opt(bol("is-tablet-pad")),
opt(bol("is-gesture")),
),
(opt(bol("is-switch")),),
))?;
if let Some(tag) = tag {
self.0.used.borrow_mut().inputs.push(tag.into());
}
Ok(InputMatch::All {
tag: tag.despan_into(),
name: name.despan_into(),
syspath: syspath.despan_into(),
devnode: devnode.despan_into(),
is_keyboard: is_keyboard.despan(),
is_pointer: is_pointer.despan(),
is_touch: is_touch.despan(),
is_tablet_tool: is_tablet_tool.despan(),
is_tablet_pad: is_tablet_pad.despan(),
is_gesture: is_gesture.despan(),
is_switch: is_switch.despan(),
})
}
}

View file

@ -0,0 +1,123 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
ConfigKeymap,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::{
config_dir,
keyboard::{parse_keymap, Keymap},
},
std::{io, path::PathBuf},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum KeymapParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error("The keymap is invalid")]
Invalid,
#[error("Keymap table must contain at least one of `name`, `map`")]
MissingField,
#[error("Keymap must have both `name` and `map` fields in this context")]
DefinitionRequired,
#[error("Could not read {0}")]
ReadFile(String, #[source] io::Error),
}
pub struct KeymapParser<'a> {
pub cx: &'a Context<'a>,
pub definition: bool,
}
impl Parser for KeymapParser<'_> {
type Value = ConfigKeymap;
type Error = KeymapParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Table];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
Ok(ConfigKeymap::Literal(parse(span, string)?))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (mut name_val, mut map_val, mut path) =
ext.extract((opt(str("name")), opt(str("map")), opt(str("path"))))?;
if map_val.is_some() && path.is_some() {
log::warn!(
"Both `name` and `path` are specified. Ignoring `path`: {}",
self.cx.error3(span)
);
path = None;
}
let file_content;
if let Some(path) = path {
let mut root = PathBuf::from(config_dir());
root.push(path.value);
file_content = match std::fs::read_to_string(&root) {
Ok(c) => c,
Err(e) => {
return Err(KeymapParserError::ReadFile(root.display().to_string(), e)
.spanned(path.span))
}
};
map_val = Some(file_content.as_str().spanned(path.span));
}
if self.definition && (name_val.is_none() || map_val.is_none()) {
return Err(KeymapParserError::DefinitionRequired.spanned(span));
}
if !self.definition && map_val.is_some() {
if let Some(val) = name_val {
log::warn!(
"Cannot use both `name` and `map` in this position. Ignoring `name`: {}",
self.cx.error3(val.span)
);
}
name_val = None;
}
if let Some(name) = name_val {
if self.definition {
self.cx
.used
.borrow_mut()
.defined_keymaps
.insert(name.into());
} else {
self.cx.used.borrow_mut().keymaps.push(name.into());
}
}
let res = match (name_val, map_val) {
(Some(name_val), Some(map_val)) => ConfigKeymap::Defined {
name: name_val.value.to_string(),
map: parse(map_val.span, map_val.value)?,
},
(Some(name_val), None) => ConfigKeymap::Named(name_val.value.to_string()),
(None, Some(map_val)) => ConfigKeymap::Literal(parse(map_val.span, map_val.value)?),
(None, None) => return Err(KeymapParserError::MissingField.spanned(span)),
};
Ok(res)
}
}
fn parse(span: Span, string: &str) -> Result<Keymap, Spanned<KeymapParserError>> {
let map = parse_keymap(string);
match map.is_valid() {
true => Ok(map),
false => Err(KeymapParserError::Invalid.spanned(span)),
}
}

View file

@ -0,0 +1,37 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
toml::toml_span::{Span, SpannedExt},
},
jay_config::logging::LogLevel,
thiserror::Error,
};
pub struct LogLevelParser;
#[derive(Debug, Error)]
pub enum LogLevelParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error("Unknown log level {0}")]
Unknown(String),
}
impl Parser for LogLevelParser {
type Value = LogLevel;
type Error = LogLevelParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
use LogLevel::*;
let level = match string.to_ascii_lowercase().as_str() {
"error" => Error,
"warn" | "warning" => Warn,
"info" => Info,
"debug" => Debug,
"trace" => Trace,
_ => return Err(LogLevelParserError::Unknown(string.to_string()).spanned(span)),
};
Ok(level)
}
}

View file

@ -0,0 +1,47 @@
use {
crate::{
config::{
context::Context,
extractor::{flt, opt, s32, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
Mode,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ModeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct ModeParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ModeParser<'a> {
type Value = Mode;
type Error = ModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (width, height, refresh_rate) =
ext.extract((s32("width"), s32("height"), opt(flt("refresh-rate"))))?;
Ok(Mode {
width: width.value,
height: height.value,
refresh_rate: refresh_rate.despan(),
})
}
}

View file

@ -0,0 +1,71 @@
use {
crate::{
config::{
keysyms::KEYSYMS,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::toml_span::{Span, SpannedExt},
},
jay_config::keyboard::{
mods::{Modifiers, ALT, CAPS, CTRL, LOCK, LOGO, MOD1, MOD2, MOD3, MOD4, MOD5, NUM, SHIFT},
ModifiedKeySym,
},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ModifiedKeysymParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("You cannot use more than one non-modifier key")]
MoreThanOneSym,
#[error("You must specify exactly one non-modifier key")]
MissingSym,
#[error("Unknown keysym {0}")]
UnknownKeysym(String),
}
pub struct ModifiedKeysymParser;
impl Parser for ModifiedKeysymParser {
type Value = ModifiedKeySym;
type Error = ModifiedKeysymParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let mut modifiers = Modifiers(0);
let mut sym = None;
for part in string.split("-") {
let modifier = match part {
"shift" => SHIFT,
"lock" => LOCK,
"ctrl" => CTRL,
"mod1" => MOD1,
"mod2" => MOD2,
"mod3" => MOD3,
"mod4" => MOD4,
"mod5" => MOD5,
"caps" => CAPS,
"alt" => ALT,
"num" => NUM,
"logo" => LOGO,
_ => match KEYSYMS.get(part) {
Some(new) if sym.is_none() => {
sym = Some(*new);
continue;
}
Some(_) => return Err(ModifiedKeysymParserError::MoreThanOneSym.spanned(span)),
_ => {
return Err(ModifiedKeysymParserError::UnknownKeysym(part.to_string())
.spanned(span))
}
},
};
modifiers |= modifier;
}
match sym {
Some(s) => Ok(modifiers | s),
None => Err(ModifiedKeysymParserError::MissingSym.spanned(span)),
}
}
}

View file

@ -0,0 +1,150 @@
use {
crate::{
config::{
context::Context,
extractor::{fltorint, opt, recover, s32, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
mode::ModeParser,
output_match::{OutputMatchParser, OutputMatchParserError},
},
Output,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::video::Transform,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum OutputParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] OutputMatchParserError),
}
pub struct OutputParser<'a> {
pub cx: &'a Context<'a>,
pub name_ok: bool,
}
impl<'a> Parser for OutputParser<'a> {
type Value = Output;
type Error = OutputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (name, match_val, x, y, scale, transform, mode) = ext.extract((
opt(str("name")),
val("match"),
recover(opt(s32("x"))),
recover(opt(s32("y"))),
recover(opt(fltorint("scale"))),
recover(opt(str("transform"))),
opt(val("mode")),
))?;
let transform = match transform {
None => None,
Some(t) => match t.value {
"none" => Some(Transform::None),
"rotate-90" => Some(Transform::Rotate90),
"rotate-180" => Some(Transform::Rotate180),
"rotate-270" => Some(Transform::Rotate270),
"flip" => Some(Transform::Flip),
"flip-rotate-90" => Some(Transform::FlipRotate90),
"flip-rotate-180" => Some(Transform::FlipRotate180),
"flip-rotate-270" => Some(Transform::FlipRotate270),
_ => {
log::warn!("Unknown transform {}: {}", t.value, self.cx.error3(t.span));
None
}
},
};
let mode = match mode {
Some(mode) => match mode.parse(&mut ModeParser(self.cx)) {
Ok(m) => Some(m),
Err(e) => {
log::warn!("Could not parse mode: {}", self.cx.error(e));
None
}
},
None => None,
};
if let Some(name) = name {
if self.name_ok {
self.cx
.used
.borrow_mut()
.defined_outputs
.insert(name.into());
} else {
log::warn!(
"Output names have no effect in this position (did you mean match.name?): {}",
self.cx.error3(name.span)
);
}
}
Ok(Output {
name: name.despan().map(|v| v.to_string()),
match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?,
x: x.despan(),
y: y.despan(),
scale: scale.despan(),
transform,
mode,
})
}
}
pub struct OutputsParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for OutputsParser<'a> {
type Value = Vec<Output>;
type Error = OutputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut OutputParser {
cx: self.0,
name_ok: true,
}) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse output: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`outputs` value should be an array: {}",
self.0.error3(span)
);
OutputParser {
cx: self.0,
name_ok: true,
}
.parse_table(span, table)
.map(|v| vec![v])
}
}

View file

@ -0,0 +1,70 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
OutputMatch,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum OutputMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct OutputMatchParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for OutputMatchParser<'a> {
type Value = OutputMatch;
type Error = OutputMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(OutputMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (name, connector, serial_number, manufacturer, model) = ext.extract((
opt(str("name")),
opt(str("connector")),
opt(str("serial-number")),
opt(str("manufacturer")),
opt(str("model")),
))?;
if let Some(name) = name {
self.0.used.borrow_mut().outputs.push(name.into());
}
Ok(OutputMatch::All {
name: name.despan_into(),
connector: connector.despan_into(),
serial_number: serial_number.despan_into(),
manufacturer: manufacturer.despan_into(),
model: model.despan_into(),
})
}
}

View file

@ -0,0 +1,72 @@
use {
crate::{
config::{
context::Context,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{action::ActionParser, modified_keysym::ModifiedKeysymParser},
Action,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::keyboard::ModifiedKeySym,
std::collections::HashSet,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ShortcutsParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
}
pub struct ShortcutsParser<'a>(pub &'a Context<'a>);
impl Parser for ShortcutsParser<'_> {
type Value = Vec<(ModifiedKeySym, Action)>;
type Error = ShortcutsParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut used_keys = HashSet::<Spanned<ModifiedKeySym>>::new();
let mut res = vec![];
for (key, value) in table.iter() {
let keysym = match ModifiedKeysymParser.parse_string(key.span, &key.value) {
Ok(k) => k,
Err(e) => {
log::warn!("Could not parse keysym: {}", self.0.error(e));
continue;
}
};
let action = match value.parse(&mut ActionParser(self.0)) {
Ok(a) => a,
Err(e) => {
log::warn!(
"Could not parse action for keysym {}: {}",
key.value,
self.0.error(e)
);
continue;
}
};
let spanned = keysym.spanned(key.span);
if let Some(prev) = used_keys.get(&spanned) {
log::warn!(
"Duplicate key overrides previous definition: {}",
self.0.error3(spanned.span)
);
log::info!("Previous definition here: {}", self.0.error3(prev.span));
}
used_keys.insert(spanned);
res.push((keysym, action));
}
Ok(res)
}
}

View file

@ -0,0 +1,81 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::exec::{ExecParser, ExecParserError},
Status,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::status::MessageFormat,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum StatusParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Exec(#[from] ExecParserError),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("Expected `plain`, `pango`, or `i3bar` but found {0}")]
UnknownFormat(String),
}
pub struct StatusParser<'a>(pub &'a Context<'a>);
impl Parser for StatusParser<'_> {
type Value = Status;
type Error = StatusParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (format, exec, separator) = ext.extract((
opt(str("format")),
val("exec"),
recover(opt(str("i3bar-separator"))),
))?;
let format = match format {
Some(f) => match f.value {
"plain" => MessageFormat::Plain,
"pango" => MessageFormat::Pango,
"i3bar" => MessageFormat::I3Bar,
_ => {
return Err(
StatusParserError::UnknownFormat(f.value.to_string()).spanned(f.span)
)
}
},
_ => MessageFormat::Plain,
};
let exec = exec.parse_map(&mut ExecParser(self.0))?;
let separator = match separator {
None => None,
Some(sep) if format == MessageFormat::I3Bar => Some(sep.value.to_string()),
Some(sep) => {
log::warn!(
"Separator has no effect for format {format:?}: {}",
self.0.error3(sep.span)
);
None
}
};
Ok(Status {
format,
exec,
separator,
})
}
}

View file

@ -0,0 +1,119 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, recover, s32, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::color::ColorParser,
Theme,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
pub struct ThemeParser<'a>(pub &'a Context<'a>);
#[derive(Debug, Error)]
pub enum ThemeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
}
impl Parser for ThemeParser<'_> {
type Value = Theme;
type Error = ThemeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (
(
attention_requested_bg_color,
bg_color,
bar_bg_color,
bar_status_text_color,
border_color,
captured_focused_title_bg_color,
captured_unfocused_title_bg_color,
focused_inactive_title_bg_color,
focused_inactive_title_text_color,
focused_title_bg_color,
),
(
focused_title_text_color,
separator_color,
unfocused_title_bg_color,
unfocused_title_text_color,
border_width,
title_height,
font,
),
) = ext.extract((
(
opt(val("attention-requested-bg-color")),
opt(val("bg-color")),
opt(val("bar-bg-color")),
opt(val("bar-status-text-color")),
opt(val("border-color")),
opt(val("captured-focused-title-bg-color")),
opt(val("captured-unfocused-title-bg-color")),
opt(val("focused-inactive-title-bg-color")),
opt(val("focused-inactive-title-text-color")),
opt(val("focused-title-bg-color")),
),
(
opt(val("focused-title-text-color")),
opt(val("separator-color")),
opt(val("unfocused-title-bg-color")),
opt(val("unfocused-title-text-color")),
recover(opt(s32("border-width"))),
recover(opt(s32("title-height"))),
recover(opt(str("font"))),
),
))?;
macro_rules! color {
($e:expr) => {
match $e {
None => None,
Some(v) => match v.parse(&mut ColorParser(self.0)) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse a color: {}", self.0.error(e));
None
}
},
}
};
}
Ok(Theme {
attention_requested_bg_color: color!(attention_requested_bg_color),
bg_color: color!(bg_color),
bar_bg_color: color!(bar_bg_color),
bar_status_text_color: color!(bar_status_text_color),
border_color: color!(border_color),
captured_focused_title_bg_color: color!(captured_focused_title_bg_color),
captured_unfocused_title_bg_color: color!(captured_unfocused_title_bg_color),
focused_inactive_title_bg_color: color!(focused_inactive_title_bg_color),
focused_inactive_title_text_color: color!(focused_inactive_title_text_color),
focused_title_bg_color: color!(focused_title_bg_color),
focused_title_text_color: color!(focused_title_text_color),
separator_color: color!(separator_color),
unfocused_title_bg_color: color!(unfocused_title_bg_color),
unfocused_title_text_color: color!(unfocused_title_text_color),
border_width: border_width.despan(),
title_height: title_height.despan(),
font: font.map(|f| f.value.to_string()),
})
}
}

View file

@ -0,0 +1,63 @@
use crate::{
config::parser::{ParseResult, Parser},
toml::{toml_span::Spanned, toml_value::Value},
};
impl Spanned<&Value> {
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
self.value.parse(self.span, parser)
}
pub fn parse_map<P: Parser, E>(
&self,
parser: &mut P,
) -> Result<<P as Parser>::Value, Spanned<E>>
where
<P as Parser>::Error: Into<E>,
{
self.parse(parser).map_spanned_err(|e| e.into())
}
}
impl Spanned<Value> {
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
self.as_ref().parse(parser)
}
pub fn parse_map<P: Parser, E>(
&self,
parser: &mut P,
) -> Result<<P as Parser>::Value, Spanned<E>>
where
<P as Parser>::Error: Into<E>,
{
self.as_ref().parse_map(parser)
}
}
pub trait SpannedErrorExt {
type T;
type E;
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
where
F: FnOnce(Self::E) -> U;
}
impl<T, E> SpannedErrorExt for Result<T, Spanned<E>> {
type T = T;
type E = E;
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
where
F: FnOnce(Self::E) -> U,
{
match self {
Ok(v) => Ok(v),
Err(e) => Err(Spanned {
span: e.span,
value: f(e.value),
}),
}
}
}

View file

@ -0,0 +1,17 @@
use crate::{
config::parser::{ParseResult, Parser},
toml::{toml_span::Span, toml_value::Value},
};
impl Value {
pub fn parse<P: Parser>(&self, span: Span, parser: &mut P) -> ParseResult<P> {
match self {
Value::String(a) => parser.parse_string(span, a),
Value::Integer(a) => parser.parse_integer(span, *a),
Value::Float(a) => parser.parse_float(span, *a),
Value::Boolean(a) => parser.parse_bool(span, *a),
Value::Array(a) => parser.parse_array(span, a),
Value::Table(a) => parser.parse_table(span, a),
}
}
}

View file

@ -0,0 +1,75 @@
keymap = """
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us+inet(evdev)" };
};
"""
on-graphics-initialized = { type = "exec", exec = "mako" }
[shortcuts]
alt-h = "focus-left"
alt-j = "focus-down"
alt-k = "focus-up"
alt-l = "focus-right"
alt-shift-h = "move-left"
alt-shift-j = "move-down"
alt-shift-k = "move-up"
alt-shift-l = "move-right"
alt-d = "split-horizontal"
alt-v = "split-vertical"
alt-t = "toggle-split"
alt-m = "toggle-mono"
alt-u = "toggle-fullscreen"
alt-f = "focus-parent"
alt-shift-c = "close"
alt-shift-f = "toggle-floating"
Super_L = { type = "exec", exec = "alacritty" }
alt-p = { type = "exec", exec = "bemenu-run" }
alt-q = "quit"
alt-shift-r = "reload-config-toml"
ctrl-alt-F1 = { type = "switch-to-vt", num = 1 }
ctrl-alt-F2 = { type = "switch-to-vt", num = 2 }
ctrl-alt-F3 = { type = "switch-to-vt", num = 3 }
ctrl-alt-F4 = { type = "switch-to-vt", num = 4 }
ctrl-alt-F5 = { type = "switch-to-vt", num = 5 }
ctrl-alt-F6 = { type = "switch-to-vt", num = 6 }
ctrl-alt-F7 = { type = "switch-to-vt", num = 7 }
ctrl-alt-F8 = { type = "switch-to-vt", num = 8 }
ctrl-alt-F9 = { type = "switch-to-vt", num = 9 }
ctrl-alt-F10 = { type = "switch-to-vt", num = 10 }
ctrl-alt-F11 = { type = "switch-to-vt", num = 11 }
ctrl-alt-F12 = { type = "switch-to-vt", num = 12 }
alt-F1 = { type = "show-workspace", name = "1" }
alt-F2 = { type = "show-workspace", name = "2" }
alt-F3 = { type = "show-workspace", name = "3" }
alt-F4 = { type = "show-workspace", name = "4" }
alt-F5 = { type = "show-workspace", name = "5" }
alt-F6 = { type = "show-workspace", name = "6" }
alt-F7 = { type = "show-workspace", name = "7" }
alt-F8 = { type = "show-workspace", name = "8" }
alt-F9 = { type = "show-workspace", name = "9" }
alt-F10 = { type = "show-workspace", name = "10" }
alt-F11 = { type = "show-workspace", name = "11" }
alt-F12 = { type = "show-workspace", name = "12" }
alt-shift-F1 = { type = "move-to-workspace", name = "1" }
alt-shift-F2 = { type = "move-to-workspace", name = "2" }
alt-shift-F3 = { type = "move-to-workspace", name = "3" }
alt-shift-F4 = { type = "move-to-workspace", name = "4" }
alt-shift-F5 = { type = "move-to-workspace", name = "5" }
alt-shift-F6 = { type = "move-to-workspace", name = "6" }
alt-shift-F7 = { type = "move-to-workspace", name = "7" }
alt-shift-F8 = { type = "move-to-workspace", name = "8" }
alt-shift-F9 = { type = "move-to-workspace", name = "9" }
alt-shift-F10 = { type = "move-to-workspace", name = "10" }
alt-shift-F11 = { type = "move-to-workspace", name = "11" }
alt-shift-F12 = { type = "move-to-workspace", name = "12" }

819
toml-config/src/lib.rs Normal file
View file

@ -0,0 +1,819 @@
#![allow(clippy::len_zero, clippy::single_char_pattern, clippy::collapsible_if)]
mod config;
mod toml;
use {
crate::config::{
parse_config, Action, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
SimpleCommand, Status, Theme,
},
ahash::{AHashMap, AHashSet},
error_reporter::Report,
jay_config::{
config, config_dir,
exec::{set_env, unset_env, Command},
get_workspace,
input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat},
is_reload,
keyboard::{Keymap, ModifiedKeySym},
logging::set_log_level,
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, set_idle,
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
switch_to_vt,
theme::{reset_colors, reset_font, reset_sizes, set_font},
video::{
connectors, drm_devices, on_connector_connected, on_graphics_initialized,
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
Connector, DrmDevice,
},
},
std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc},
};
fn default_seat() -> Seat {
get_seat("default")
}
impl Action {
fn into_fn(self, state: &Rc<State>) -> Box<dyn FnMut()> {
let s = state.persistent.seat;
match self {
Action::SimpleCommand { cmd } => match cmd {
SimpleCommand::Focus(dir) => Box::new(move || s.focus(dir)),
SimpleCommand::Move(dir) => Box::new(move || s.move_(dir)),
SimpleCommand::Split(axis) => Box::new(move || s.create_split(axis)),
SimpleCommand::ToggleSplit => Box::new(move || s.toggle_split()),
SimpleCommand::ToggleMono => Box::new(move || s.toggle_mono()),
SimpleCommand::ToggleFullscreen => Box::new(move || s.toggle_fullscreen()),
SimpleCommand::FocusParent => Box::new(move || s.focus_parent()),
SimpleCommand::Close => Box::new(move || s.close()),
SimpleCommand::DisablePointerConstraint => {
Box::new(move || s.disable_pointer_constraint())
}
SimpleCommand::ToggleFloating => Box::new(move || s.toggle_floating()),
SimpleCommand::Quit => Box::new(quit),
SimpleCommand::ReloadConfigToml => {
let persistent = state.persistent.clone();
Box::new(move || load_config(false, &persistent))
}
SimpleCommand::ReloadConfigSo => Box::new(reload),
SimpleCommand::None => Box::new(|| ()),
},
Action::Multi { actions } => {
let mut actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
Box::new(move || {
for action in &mut actions {
action();
}
})
}
Action::Exec { exec } => Box::new(move || create_command(&exec).spawn()),
Action::SwitchToVt { num } => Box::new(move || switch_to_vt(num)),
Action::ShowWorkspace { name } => {
let workspace = get_workspace(&name);
Box::new(move || s.show_workspace(workspace))
}
Action::ConfigureConnector { con } => Box::new(move || {
for c in connectors() {
if con.match_.matches(c) {
con.apply(c);
}
}
}),
Action::ConfigureInput { input } => {
let state = state.clone();
Box::new(move || {
for c in input_devices() {
if input.match_.matches(c, &state) {
input.apply(c);
}
}
})
}
Action::ConfigureOutput { out } => {
let state = state.clone();
Box::new(move || {
for c in connectors() {
if out.match_.matches(c, &state) {
out.apply(c);
}
}
})
}
Action::SetEnv { env } => Box::new(move || {
for (k, v) in &env {
set_env(k, v);
}
}),
Action::UnsetEnv { env } => Box::new(move || {
for k in &env {
unset_env(k);
}
}),
Action::SetKeymap { map } => {
let state = state.clone();
Box::new(move || state.set_keymap(&map))
}
Action::SetStatus { status } => {
let state = state.clone();
Box::new(move || state.set_status(&status))
}
Action::SetTheme { theme } => {
let state = state.clone();
Box::new(move || state.apply_theme(&theme))
}
Action::SetLogLevel { level } => Box::new(move || set_log_level(level)),
Action::SetGfxApi { api } => Box::new(move || set_gfx_api(api)),
Action::ConfigureDirectScanout { enabled } => {
Box::new(move || set_direct_scanout_enabled(enabled))
}
Action::ConfigureDrmDevice { dev } => {
let state = state.clone();
Box::new(move || {
for d in drm_devices() {
if dev.match_.matches(d, &state) {
dev.apply(d);
}
}
})
}
Action::SetRenderDevice { dev } => {
let state = state.clone();
Box::new(move || {
for d in drm_devices() {
if dev.matches(d, &state) {
d.make_render_device();
}
}
})
}
Action::ConfigureIdle { idle } => Box::new(move || set_idle(Some(idle))),
}
}
}
fn apply_recursive_match<'a, U>(
type_name: &str,
list: &'a AHashMap<String, U>,
active: &mut AHashSet<&'a str>,
name: &'a str,
matches: impl FnOnce(&'a U, &mut AHashSet<&'a str>) -> bool,
) -> bool {
match list.get(name) {
None => {
log::warn!("{type_name} with name {name} does not exist");
false
}
Some(m) => {
if active.insert(name) {
let matches = matches(m, active);
active.remove(name);
matches
} else {
log::warn!("Recursion while evaluating match for {type_name} {name}");
false
}
}
}
}
impl ConfigDrmDevice {
fn apply(&self, d: DrmDevice) {
if let Some(api) = self.gfx_api {
d.set_gfx_api(api);
}
if let Some(dse) = self.direct_scanout_enabled {
d.set_direct_scanout_enabled(dse);
}
}
}
impl DrmDeviceMatch {
fn matches(&self, d: DrmDevice, state: &State) -> bool {
self.matches_(d, state, &mut AHashSet::new())
}
fn matches_<'a>(
&'a self,
d: DrmDevice,
state: &'a State,
active: &mut AHashSet<&'a str>,
) -> bool {
match self {
DrmDeviceMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
DrmDeviceMatch::All {
name,
syspath,
vendor,
vendor_name,
model,
model_name,
devnode,
} => {
if let Some(name) = name {
let matches = apply_recursive_match(
"drm device",
&state.drm_devices,
active,
name,
|m, active| m.matches_(d, state, active),
);
if !matches {
return false;
}
}
if let Some(syspath) = syspath {
if d.syspath() != *syspath {
return false;
}
}
if let Some(devnode) = devnode {
if d.devnode() != *devnode {
return false;
}
}
if let Some(model) = model_name {
if d.model() != *model {
return false;
}
}
if let Some(vendor) = vendor_name {
if d.vendor() != *vendor {
return false;
}
}
if let Some(vendor) = vendor {
if d.pci_id().vendor != *vendor {
return false;
}
}
if let Some(model) = model {
if d.pci_id().model != *model {
return false;
}
}
true
}
}
}
}
impl InputMatch {
fn matches(&self, d: InputDevice, state: &State) -> bool {
self.matches_(d, state, &mut AHashSet::new())
}
fn matches_<'a>(
&'a self,
d: InputDevice,
state: &'a State,
active: &mut AHashSet<&'a str>,
) -> bool {
match self {
InputMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
InputMatch::All {
tag,
name,
syspath,
devnode,
is_keyboard,
is_pointer,
is_touch,
is_tablet_tool,
is_tablet_pad,
is_gesture,
is_switch,
} => {
if let Some(name) = name {
if d.name() != *name {
return false;
}
}
if let Some(tag) = tag {
let matches = apply_recursive_match(
"input device",
&state.input_devices,
active,
tag,
|m, active| m.matches_(d, state, active),
);
if !matches {
return false;
}
}
if let Some(syspath) = syspath {
if d.syspath() != *syspath {
return false;
}
}
if let Some(devnode) = devnode {
if d.devnode() != *devnode {
return false;
}
}
macro_rules! check_cap {
($is:expr, $cap:ident) => {
if let Some(is) = *$is {
if d.has_capability(jay_config::input::capability::$cap) != is {
return false;
}
}
};
}
check_cap!(is_keyboard, CAP_KEYBOARD);
check_cap!(is_pointer, CAP_POINTER);
check_cap!(is_touch, CAP_TOUCH);
check_cap!(is_tablet_tool, CAP_TABLET_TOOL);
check_cap!(is_tablet_pad, CAP_TABLET_PAD);
check_cap!(is_gesture, CAP_GESTURE);
check_cap!(is_switch, CAP_SWITCH);
true
}
}
}
}
impl Input {
fn apply(&self, c: InputDevice) {
if let Some(v) = self.accel_profile {
c.set_accel_profile(v);
}
if let Some(v) = self.accel_speed {
c.set_accel_speed(v);
}
if let Some(v) = self.tap_enabled {
c.set_tap_enabled(v);
}
if let Some(v) = self.tap_drag_enabled {
c.set_drag_enabled(v);
}
if let Some(v) = self.tap_drag_lock_enabled {
c.set_drag_lock_enabled(v);
}
if let Some(v) = self.left_handed {
c.set_left_handed(v);
}
if let Some(v) = self.natural_scrolling {
c.set_natural_scrolling_enabled(v);
}
if let Some(v) = self.px_per_wheel_scroll {
c.set_px_per_wheel_scroll(v);
}
if let Some(v) = self.transform_matrix {
c.set_transform_matrix(v);
}
}
}
impl OutputMatch {
fn matches(&self, c: Connector, state: &State) -> bool {
if !c.connected() {
return false;
}
self.matches_(c, state, &mut AHashSet::new())
}
fn matches_<'a>(
&'a self,
c: Connector,
state: &'a State,
active: &mut AHashSet<&'a str>,
) -> bool {
match self {
OutputMatch::Any(m) => m.iter().any(|m| m.matches_(c, state, active)),
OutputMatch::All {
name,
connector,
serial_number,
manufacturer,
model,
} => {
if let Some(name) = name {
let matches = apply_recursive_match(
"output",
&state.outputs,
active,
name,
|m, active| m.matches_(c, state, active),
);
if !matches {
return false;
}
}
if let Some(connector) = &connector {
if c.name() != *connector {
return false;
}
}
if let Some(serial_number) = &serial_number {
if c.serial_number() != *serial_number {
return false;
}
}
if let Some(manufacturer) = &manufacturer {
if c.manufacturer() != *manufacturer {
return false;
}
}
if let Some(model) = &model {
if c.model() != *model {
return false;
}
}
true
}
}
}
}
impl ConnectorMatch {
fn matches(&self, c: Connector) -> bool {
if !c.exists() {
return false;
}
match self {
ConnectorMatch::Any(m) => m.iter().any(|m| m.matches(c)),
ConnectorMatch::All { connector } => {
if let Some(connector) = &connector {
if c.name() != *connector {
return false;
}
}
true
}
}
}
}
impl ConfigConnector {
fn apply(&self, c: Connector) {
c.set_enabled(self.enabled);
}
}
impl Output {
fn apply(&self, c: Connector) {
if self.x.is_some() || self.y.is_some() {
let (old_x, old_y) = c.position();
c.set_position(self.x.unwrap_or(old_x), self.y.unwrap_or(old_y));
}
if let Some(scale) = self.scale {
c.set_scale(scale);
}
if let Some(transform) = self.transform {
c.set_transform(transform);
}
if let Some(mode) = &self.mode {
let modes = c.modes();
let m = modes.iter().find(|m| {
if m.width() != mode.width || m.height() != mode.height {
return false;
}
match mode.refresh_rate {
None => true,
Some(rr) => m.refresh_rate() as f64 / 1000.0 == rr,
}
});
match m {
None => {
log::warn!("Output {} does not support mode {mode}", c.name());
}
Some(m) => c.set_mode(m.width(), m.height(), Some(m.refresh_rate())),
}
}
}
}
struct State {
outputs: AHashMap<String, OutputMatch>,
drm_devices: AHashMap<String, DrmDeviceMatch>,
input_devices: AHashMap<String, InputMatch>,
persistent: Rc<PersistentState>,
keymaps: AHashMap<String, Keymap>,
}
impl Drop for State {
fn drop(&mut self) {
for keymap in self.keymaps.values() {
keymap.destroy();
}
}
}
impl State {
fn unbind_all(&self) {
let mut binds = self.persistent.binds.borrow_mut();
for bind in binds.drain() {
self.persistent.seat.unbind(bind);
}
}
fn apply_shortcuts(
self: &Rc<Self>,
shortcuts: impl IntoIterator<Item = (ModifiedKeySym, Action)>,
) {
let mut binds = self.persistent.binds.borrow_mut();
for (key, value) in shortcuts {
if let Action::SimpleCommand {
cmd: SimpleCommand::None,
} = value
{
self.persistent.seat.unbind(key);
binds.remove(&key);
} else {
self.persistent.seat.bind(key, value.into_fn(self));
binds.insert(key);
}
}
}
fn set_keymap(&self, map: &ConfigKeymap) {
let map = match map {
ConfigKeymap::Named(n) => match self.keymaps.get(n) {
None => {
log::warn!("Unknown keymap {n}");
return;
}
Some(m) => *m,
},
ConfigKeymap::Defined { map, .. } => *map,
ConfigKeymap::Literal(map) => *map,
};
self.persistent.seat.set_keymap(map);
}
fn set_status(&self, status: &Option<Status>) {
set_status("");
match status {
None => unset_status_command(),
Some(s) => {
set_i3bar_separator(s.separator.as_deref().unwrap_or(" | "));
set_status_command(s.format, create_command(&s.exec))
}
}
}
fn apply_theme(&self, theme: &Theme) {
use jay_config::theme::{colors::*, sized::*};
macro_rules! color {
($colorable:ident, $field:ident) => {
if let Some(color) = theme.$field {
$colorable.set_color(color)
}
};
}
color!(
ATTENTION_REQUESTED_BACKGROUND_COLOR,
attention_requested_bg_color
);
color!(BACKGROUND_COLOR, bg_color);
color!(BAR_BACKGROUND_COLOR, bar_bg_color);
color!(BAR_STATUS_TEXT_COLOR, bar_status_text_color);
color!(BORDER_COLOR, border_color);
color!(
CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
captured_focused_title_bg_color
);
color!(
CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
captured_unfocused_title_bg_color
);
color!(
FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
focused_inactive_title_bg_color
);
color!(
FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
focused_inactive_title_text_color
);
color!(FOCUSED_TITLE_BACKGROUND_COLOR, focused_title_bg_color);
color!(FOCUSED_TITLE_TEXT_COLOR, focused_title_text_color);
color!(SEPARATOR_COLOR, separator_color);
color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
macro_rules! size {
($sized:ident, $field:ident) => {
if let Some(size) = theme.$field {
$sized.set(size);
}
};
}
size!(BORDER_WIDTH, border_width);
size!(TITLE_HEIGHT, title_height);
if let Some(font) = &theme.font {
set_font(font);
}
}
}
#[derive(Eq, PartialEq, Hash)]
struct OutputId {
manufacturer: String,
model: String,
serial_number: String,
}
struct PersistentState {
seen_outputs: RefCell<AHashSet<OutputId>>,
default: Config,
seat: Seat,
binds: RefCell<AHashSet<ModifiedKeySym>>,
}
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
let mut path = PathBuf::from(config_dir());
path.push("config.toml");
let config = match std::fs::read(&path) {
Ok(input) => match parse_config(&input, |e| {
log::warn!("Error while parsing {}: {}", path.display(), Report::new(e))
}) {
None if initial_load => {
log::warn!("Using default config instead");
persistent.default.clone()
}
None => {
log::warn!("Ignoring config reload");
return;
}
Some(c) => c,
},
Err(e) if e.kind() == ErrorKind::NotFound => {
log::info!("{} does not exist. Using default config.", path.display());
persistent.default.clone()
}
Err(e) => {
log::warn!("Could not load {}: {}", path.display(), Report::new(e));
log::warn!("Ignoring config reload");
return;
}
};
let mut outputs = AHashMap::new();
for output in &config.outputs {
if let Some(name) = &output.name {
let prev = outputs.insert(name.clone(), output.match_.clone());
if prev.is_some() {
log::warn!("Duplicate output name {name}");
}
}
}
let mut keymaps = AHashMap::new();
for keymap in config.keymaps {
match keymap {
ConfigKeymap::Defined { name, map } => {
keymaps.insert(name, map);
}
_ => log::warn!("Keymap is not in defined form in top-level context"),
}
}
let mut input_devices = AHashMap::new();
for input in &config.inputs {
if let Some(tag) = &input.tag {
let prev = input_devices.insert(tag.clone(), input.match_.clone());
if prev.is_some() {
log::warn!("Duplicate input tag {tag}");
}
}
}
let mut named_drm_device = AHashMap::new();
for drm_device in &config.drm_devices {
if let Some(name) = &drm_device.name {
let prev = named_drm_device.insert(name.clone(), drm_device.match_.clone());
if prev.is_some() {
log::warn!("Duplicate drm device name {name}");
}
}
}
let state = Rc::new(State {
outputs,
drm_devices: named_drm_device,
input_devices,
persistent: persistent.clone(),
keymaps,
});
state.set_status(&config.status);
match config.on_graphics_initialized {
None => on_graphics_initialized(|| ()),
Some(a) => on_graphics_initialized(a.into_fn(&state)),
}
match config.on_idle {
None => on_idle(|| ()),
Some(a) => on_idle(a.into_fn(&state)),
}
state.unbind_all();
state.apply_shortcuts(config.shortcuts);
if let Some(keymap) = config.keymap {
state.set_keymap(&keymap);
}
on_new_connector(move |c| {
for connector in &config.connectors {
if connector.match_.matches(c) {
connector.apply(c);
}
}
});
on_connector_connected({
let state = state.clone();
move |c| {
let id = OutputId {
manufacturer: c.manufacturer(),
model: c.model(),
serial_number: c.serial_number(),
};
if state.persistent.seen_outputs.borrow_mut().insert(id) {
for output in &config.outputs {
if output.match_.matches(c, &state) {
output.apply(c);
}
}
}
}
});
set_default_workspace_capture(config.workspace_capture);
for (k, v) in config.env {
set_env(&k, &v);
}
if initial_load && !is_reload() {
if let Some(on_startup) = config.on_startup {
on_startup.into_fn(&state)();
}
if let Some(level) = config.log_level {
set_log_level(level);
}
if let Some(idle) = config.idle {
set_idle(Some(idle));
}
}
on_devices_enumerated({
let state = state.clone();
move || {
if let Some(dev) = config.render_device {
for d in drm_devices() {
if dev.matches(d, &state) {
d.make_render_device();
return;
}
}
}
}
});
reset_colors();
reset_font();
reset_sizes();
state.apply_theme(&config.theme);
if let Some(api) = config.gfx_api {
set_gfx_api(api);
}
if let Some(dse) = config.direct_scanout_enabled {
set_direct_scanout_enabled(dse);
}
on_new_drm_device({
let state = state.clone();
move |d| {
for dev in &config.drm_devices {
if dev.match_.matches(d, &state) {
dev.apply(d);
}
}
}
});
on_new_input_device({
let state = state.clone();
move |c| {
for input in &config.inputs {
if input.match_.matches(c, &state) {
input.apply(c);
}
}
}
});
}
fn create_command(exec: &Exec) -> Command {
let mut command = Command::new(&exec.prog);
for arg in &exec.args {
command.arg(arg);
}
for (k, v) in &exec.envs {
command.env(k, v);
}
command
}
const DEFAULT: &[u8] = include_bytes!("default-config.toml");
pub fn configure() {
let default = parse_config(DEFAULT, |e| {
panic!("Could not parse the default config: {}", Report::new(e))
});
let persistent = Rc::new(PersistentState {
seen_outputs: Default::default(),
default: default.unwrap(),
seat: default_seat(),
binds: Default::default(),
});
load_config(true, &persistent);
}
config!(configure);

6
toml-config/src/toml.rs Normal file
View file

@ -0,0 +1,6 @@
#[cfg(test)]
mod tests;
mod toml_lexer;
pub mod toml_parser;
pub mod toml_span;
pub mod toml_value;

View file

@ -0,0 +1,133 @@
use {
crate::{
config::error::SpannedError,
toml::{
toml_parser::{parse, ErrorHandler, ParserError},
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
bstr::{BStr, ByteSlice},
std::{
convert::Infallible,
os::unix::ffi::OsStrExt,
panic::{catch_unwind, AssertUnwindSafe},
str::FromStr,
},
walkdir::WalkDir,
};
#[test]
fn test() {
let mut have_failures = false;
let mut num = 0;
for path in WalkDir::new("./toml-test/tests/valid") {
let path = path.unwrap();
if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") {
num += 1;
let res = catch_unwind(AssertUnwindSafe(|| {
have_failures |= run_test(prefix.as_bstr());
}));
if res.is_err() {
eprintln!("panic while running {}", prefix.as_bstr());
}
}
}
if have_failures {
panic!("There were test failures");
}
eprintln!("ran {num} tests");
}
fn run_test(prefix: &BStr) -> bool {
let toml = std::fs::read(&format!("{}.toml", prefix)).unwrap();
let json = std::fs::read_to_string(&format!("{}.json", prefix)).unwrap();
let json: serde_json::Value = serde_json::from_str(&json).unwrap();
let json_as_toml = json_to_value(json);
let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix, &toml)) {
Ok(t) => t,
Err(e) => {
eprintln!("toml could not be parsed in test {}", prefix);
NoErrorHandler(prefix, &toml).handle(e);
return true;
}
};
if toml != json_as_toml {
eprintln!("toml and json differ in test {}", prefix);
eprintln!("toml: {:#?}", toml);
eprintln!("json: {:#?}", json_as_toml);
true
} else {
false
}
}
fn json_to_value(json: serde_json::Value) -> Spanned<Value> {
let span = Span { lo: 0, hi: 0 };
let val = match json {
serde_json::Value::String(_)
| serde_json::Value::Number(_)
| serde_json::Value::Null
| serde_json::Value::Bool(_) => panic!("Unexpected type"),
serde_json::Value::Array(v) => Value::Array(v.into_iter().map(json_to_value).collect()),
serde_json::Value::Object(v) => {
if v.len() == 2 && v.contains_key("type") && v.contains_key("value") {
let ty = v.get("type").unwrap().as_str().unwrap();
let val = v.get("value").unwrap().as_str().unwrap();
match ty {
"string" => Value::String(val.to_owned()),
"integer" => Value::Integer(i64::from_str(val).unwrap()),
"float" => Value::Float(f64::from_str(val).unwrap()),
"bool" => Value::Boolean(bool::from_str(val).unwrap()),
_ => panic!("unexpected type {}", ty),
}
} else {
Value::Table(
v.into_iter()
.map(|(k, v)| (k.spanned(span), json_to_value(v)))
.collect(),
)
}
}
};
val.spanned(span)
}
struct NoErrorHandler<'a>(&'a BStr, &'a [u8]);
impl<'a> ErrorHandler for NoErrorHandler<'a> {
fn handle(&self, err: Spanned<ParserError>) {
eprintln!(
"{}: An error occurred during validation: {}",
self.0,
SpannedError {
input: self.1.into(),
span: err.span,
cause: Some(err.value),
}
);
}
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
eprintln!(
"{}: Redefinition: {}",
self.0,
SpannedError {
input: self.1.into(),
span: err.span,
cause: Some(err.value),
}
);
eprintln!(
"{}: Previous: {}",
self.0,
SpannedError {
input: self.1.into(),
span: prev,
cause: None::<Infallible>,
}
);
}
}

View file

@ -0,0 +1,193 @@
use crate::toml::toml_span::{Span, Spanned, SpannedExt};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Token<'a> {
Dot,
Equals,
Comma,
LeftBracket,
RightBracket,
LeftBrace,
RightBrace,
LiteralString(&'a [u8]),
CookedString(&'a [u8]),
Literal(&'a [u8]),
}
impl<'a> Token<'a> {
pub fn name(self, value_context: bool) -> &'static str {
match self {
Token::Dot => "`.`",
Token::Equals => "`=`",
Token::Comma => "`,`",
Token::LeftBracket => "`[`",
Token::RightBracket => "`]`",
Token::LeftBrace => "`{`",
Token::RightBrace => "`}`",
Token::LiteralString(_) | Token::CookedString(_) => "a string",
Token::Literal(_) if value_context => "a literal",
Token::Literal(_) => "a key",
}
}
}
pub struct Lexer<'a> {
input: &'a [u8],
pos: usize,
peek: Option<Spanned<Token<'a>>>,
peek_value_context: bool,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a [u8]) -> Self {
Self {
input,
pos: 0,
peek: None,
peek_value_context: false,
}
}
pub fn pos(&mut self) -> usize {
self.skip_ws();
self.pos
}
fn skip_ws(&mut self) {
while let Some(char) = self.input.get(self.pos).copied() {
match char {
b' ' | b'\t' | b'\n' => self.pos += 1,
b'#' => {
self.pos += 1;
while let Some(char) = self.input.get(self.pos).copied() {
self.pos += 1;
if char == b'\n' {
break;
}
}
}
_ => break,
}
}
}
pub fn peek(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
let next = self.next(value_context);
self.peek = next;
self.peek_value_context = value_context;
next
}
pub fn next(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
if let Some(peek) = self.peek.take() {
if self.peek_value_context == value_context {
return Some(peek);
}
self.pos = peek.span.lo;
}
use Token::*;
macro_rules! get {
($off:expr) => {
self.input.get(self.pos + $off).copied()
};
}
self.skip_ws();
let Some(c) = get!(0) else {
return None;
};
let pos = self.pos;
macro_rules! span {
() => {
Span {
lo: pos,
hi: self.pos,
}
};
}
'simple: {
let t = match c {
b'.' => Dot,
b',' => Comma,
b'=' => Equals,
b'[' => LeftBracket,
b']' => RightBracket,
b'{' => LeftBrace,
b'}' => RightBrace,
_ => break 'simple,
};
self.pos += 1;
return Some(t.spanned(span!()));
}
macro_rules! try_string {
($delim:expr, $escaping:expr, $ident:ident) => {
if c == $delim {
'ml_string: {
let delim = ($delim, Some($delim), Some($delim));
if (c, get!(1), get!(2)) != delim {
break 'ml_string;
}
self.pos += 3;
if get!(0) == Some(b'\n') {
self.pos += 1;
}
let start = self.pos;
let end = loop {
let c = match get!(0) {
Some(c) => c,
_ => break self.pos,
};
self.pos += 1;
if $escaping && c == b'\\' {
self.pos += 1;
} else if c == $delim {
if (c, get!(0), get!(1)) == delim && get!(2) != Some($delim) {
self.pos += 2;
break self.pos - 3;
}
}
};
return Some($ident(&self.input[start..end]).spanned(span!()));
}
self.pos += 1;
let start = self.pos;
let end = loop {
let c = match get!(0) {
Some(c) => c,
_ => break self.pos,
};
self.pos += 1;
if $escaping && c == b'\\' {
self.pos += 1;
} else if c == $delim {
break self.pos - 1;
}
};
return Some($ident(&self.input[start..end]).spanned(span!()));
}
};
}
try_string!(b'\'', false, LiteralString);
try_string!(b'"', true, CookedString);
let start = self.pos;
while let Some(c) = get!(0) {
match c {
b' ' | b'\t' | b'\n' | b'#' | b',' | b'=' | b'{' | b'}' | b'[' | b']' => break,
b'.' if !value_context => break,
_ => {}
}
self.pos += 1;
}
let end = self.pos;
Some(Literal(&self.input[start..end]).spanned(span!()))
}
}

View file

@ -0,0 +1,534 @@
use {
crate::toml::{
toml_lexer::{Lexer, Token},
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
bstr::ByteSlice,
indexmap::{
map::{raw_entry_v1::RawEntryMut, RawEntryApiV1},
IndexMap,
},
std::{collections::VecDeque, mem, str::FromStr},
thiserror::Error,
};
pub trait ErrorHandler {
fn handle(&self, err: Spanned<ParserError>);
fn redefinition(&self, err: Spanned<ParserError>, prev: Span);
}
#[derive(Debug, Error)]
pub enum ParserError {
#[error("Unexpected end of file")]
UnexpectedEof,
#[error("Expected a key")]
MissingKey,
#[error("Expected {0} but found {1}")]
Expected(&'static str, &'static str),
#[error("Duplicate key overwrites the previous definition")]
Redefined,
#[error("Literal is not valid UTF-8")]
NonUtf8Literal,
#[error("Could not parse the literal")]
UnknownLiteral,
#[error("Ignoring key due to following error")]
IgnoringKey,
#[error("Unnecessary comma")]
UnnecessaryComma,
}
pub fn parse(
input: &[u8],
error_handler: &dyn ErrorHandler,
) -> Result<Spanned<Value>, Spanned<ParserError>> {
let parser = Parser {
lexer: Lexer::new(input),
error_handler,
last_span: None,
};
parser.parse()
}
struct Parser<'a, 'b> {
lexer: Lexer<'a>,
error_handler: &'b dyn ErrorHandler,
last_span: Option<Span>,
}
type Key = VecDeque<Spanned<String>>;
impl<'a, 'b> Parser<'a, 'b> {
fn parse(mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
self.parse_document()
}
fn unexpected_eof(&self) -> Spanned<ParserError> {
let span = self.last_span.unwrap_or(Span { lo: 0, hi: 0 });
ParserError::UnexpectedEof.spanned(span)
}
fn next(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
match self.lexer.next(value_context) {
Some(t) => {
self.last_span = Some(t.span);
Ok(t)
}
_ => Err(self.unexpected_eof()),
}
}
fn peek(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
match self.lexer.peek(value_context) {
Some(t) => Ok(t),
_ => Err(self.unexpected_eof()),
}
}
fn parse_value(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
let token = self.peek(true)?;
match token.value {
Token::LiteralString(s) => self.parse_literal_string(s),
Token::CookedString(s) => self.parse_cooked_string(s),
Token::LeftBracket => self.parse_array(),
Token::Literal(l) => self.parse_literal_value(l),
Token::LeftBrace => self.parse_inline_table(),
Token::Dot | Token::Equals | Token::Comma | Token::RightBrace | Token::RightBracket => {
Err(ParserError::Expected("a value", token.value.name(true)).spanned(token.span))
}
}
}
fn parse_literal_value(
&mut self,
literal: &[u8],
) -> Result<Spanned<Value>, Spanned<ParserError>> {
let span = self.next(true)?.span;
let Ok(s) = std::str::from_utf8(literal) else {
return Err(ParserError::NonUtf8Literal.spanned(span));
};
if s == "true" {
return Ok(Value::Boolean(true).spanned(span));
}
if s == "false" {
return Ok(Value::Boolean(false).spanned(span));
}
let s = s.replace('_', "");
if let Ok(n) = i64::from_str(&s) {
return Ok(Value::Integer(n).spanned(span));
}
'radix: {
let b = s.as_bytes();
if b.len() >= 2 && b[0] == b'0' {
let radix = match b[1] {
b'x' => 16,
b'o' => 8,
b'b' => 2,
_ => break 'radix,
};
if let Ok(n) = i64::from_str_radix(&s[2..], radix) {
return Ok(Value::Integer(n).spanned(span));
}
}
}
if let Ok(n) = f64::from_str(&s) {
return Ok(Value::Float(n).spanned(span));
}
Err(ParserError::UnknownLiteral.spanned(span))
}
fn parse_literal_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
let span = self.next(true)?.span;
let s = s.as_bstr().to_string();
Ok(Value::String(s).spanned(span))
}
fn parse_cooked_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
let span = self.next(true)?.span;
let s = self.cook_string(s);
Ok(Value::String(s).spanned(span))
}
fn cook_string(&self, s: &[u8]) -> String {
use std::io::Write;
if !s.contains(&b'\\') {
return s.as_bstr().to_string();
}
let mut res = vec![];
let mut pos = 0;
while pos < s.len() {
let c = s[pos];
pos += 1;
match c {
b'\\' => {
let c = s[pos];
pos += 1;
match c {
b'\\' => res.push(b'\\'),
b'"' => res.push(b'"'),
b'b' => res.push(0x8),
b'f' => res.push(0xc),
b'n' => res.push(b'\n'),
b'r' => res.push(b'\r'),
b't' => res.push(b'\t'),
b'e' => res.push(0x1b),
b'x' | b'u' | b'U' => 'unicode: {
let len = match c {
b'x' => 2,
b'u' => 4,
_ => 8,
};
if s.len() - pos >= len {
if let Ok(s) = std::str::from_utf8(&s[pos..pos + len]) {
if let Ok(n) = u32::from_str_radix(s, 16) {
if let Some(c) = char::from_u32(n) {
pos += len;
let _ = write!(res, "{}", c);
break 'unicode;
}
}
}
}
res.extend_from_slice(&s[pos - 2..]);
}
b' ' | b'\t' | b'\n' => {
let mut t = pos;
let mut saw_nl = c == b'\n';
while t < s.len() && matches!(s[t], b' ' | b'\t' | b'\n') {
saw_nl |= s[t] == b'\n';
t += 1;
}
if saw_nl {
pos = t;
} else {
res.extend_from_slice(&[b'\\', c]);
}
}
_ => {
res.extend_from_slice(&[b'\\', c]);
}
}
}
_ => res.push(c),
}
}
res.as_bstr().to_string()
}
fn parse_array(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
let lo = self.next(true)?.span.lo;
let mut entries = vec![];
let mut consumed_comma = false;
loop {
if let Some(v) = self.lexer.peek(true) {
if v.value == Token::RightBracket {
let _ = self.next(true);
let hi = v.span.hi;
let span = Span { lo, hi };
return Ok(Value::Array(entries).spanned(span));
}
if entries.len() > 0 && !mem::take(&mut consumed_comma) {
self.error_handler.handle(
ParserError::Expected("`,` or `]`", v.value.name(true)).spanned(v.span),
);
}
}
match self.parse_value() {
Ok(v) => {
entries.push(v);
consumed_comma = self.skip_comma(true);
}
Err(e) => {
self.skip_tree(Token::LeftBracket, Token::RightBracket);
return Err(e);
}
}
}
}
fn parse_inline_table(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
let lo = self.next(true)?.span.lo;
let mut map = IndexMap::new();
let mut consumed_comma = false;
loop {
let token = match self.peek(false) {
Ok(t) => t,
Err(e) => {
self.error_handler.handle(e);
break;
}
};
if token.value == Token::RightBrace {
let _ = self.next(false);
break;
}
if !map.is_empty() && !mem::take(&mut consumed_comma) {
self.error_handler.handle(
ParserError::Expected("`,` or `}`", token.value.name(false))
.spanned(token.span),
);
}
let res = match self.parse_key_value_with_recovery() {
Ok(res) => res,
Err(e) => {
self.skip_tree(Token::LeftBrace, Token::RightBrace);
return Err(e);
}
};
if let Some((mut key, value)) = res {
self.insert(&mut map, &mut key, value, false, false);
};
consumed_comma = self.skip_comma(false);
}
let hi = self.last_span().hi;
let span = Span { lo, hi };
Ok(Value::Table(map).spanned(span))
}
fn skip_comma(&mut self, value_context: bool) -> bool {
if let Some(token) = self.lexer.peek(value_context) {
if token.value != Token::Comma {
return false;
}
let _ = self.next(value_context);
}
while let Some(token) = self.lexer.peek(value_context) {
if token.value != Token::Comma {
break;
}
let _ = self.next(value_context);
self.error_handler
.handle(ParserError::UnnecessaryComma.spanned(token.span));
}
true
}
fn parse_document(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
let mut map = IndexMap::new();
self.parse_table_body(&mut map)?;
while self.lexer.peek(false).is_some() {
let (mut key, append) = self.parse_table_header()?;
let mut inner_map = IndexMap::new();
self.parse_table_body(&mut inner_map)?;
let value = Value::Table(inner_map).spanned(key.span);
self.insert(&mut map, &mut key.value, value, true, append);
}
let hi = self.last_span().hi;
let span = Span { lo: 0, hi };
Ok(Value::Table(map).spanned(span))
}
fn parse_table_header(&mut self) -> Result<(Spanned<Key>, bool), Spanned<ParserError>> {
let lo = self.next(false)?.span.lo;
let mut append = false;
if let Some(token) = self.lexer.peek(false) {
if token.value == Token::LeftBracket {
let _ = self.next(false);
append = true;
}
}
let key = self.parse_key()?;
let mut hi = self.parse_exact(Token::RightBracket, false)?.hi;
if append {
hi = self.parse_exact(Token::RightBracket, false)?.hi;
}
let span = Span { lo, hi };
Ok((key.spanned(span), append))
}
fn parse_table_body(
&mut self,
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
) -> Result<(), Spanned<ParserError>> {
while let Some(e) = self.lexer.peek(false) {
if e.value == Token::LeftBracket {
return Ok(());
}
let Some((mut key, value)) = self.parse_key_value_with_recovery()? else {
continue;
};
self.insert(dst, &mut key, value, false, false);
}
Ok(())
}
fn insert(
&self,
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
keys: &mut Key,
value: Spanned<Value>,
modify_array_element: bool,
append_last: bool,
) {
let key = keys.pop_front().unwrap();
if keys.is_empty() {
if let RawEntryMut::Occupied(mut old) =
dst.raw_entry_mut_v1().from_key(key.value.as_str())
{
if append_last {
if let Value::Array(array) = &mut old.get_mut().value {
array.push(value);
return;
}
}
if let Value::Table(old) = &mut old.get_mut().value {
if let Value::Table(new) = value.value {
for (k, v) in new {
let mut keys = Key::new();
keys.push_back(k);
self.insert(old, &mut keys, v, false, false);
}
return;
}
}
self.error_handler
.redefinition(ParserError::Redefined.spanned(key.span), old.key().span);
old.shift_remove();
}
let span = value.span;
let value = match append_last {
true => Value::Array(vec![value]).spanned(span),
false => value,
};
dst.insert(key, value);
} else {
if let RawEntryMut::Occupied(mut o) = dst.raw_entry_mut_v1().from_key(&key) {
match &mut o.get_mut().value {
Value::Table(dst) => {
self.insert(dst, keys, value, modify_array_element, append_last);
return;
}
Value::Array(array) if modify_array_element => {
if let Some(Value::Table(dst)) =
array.last_mut().as_mut().map(|v| &mut v.value)
{
self.insert(dst, keys, value, modify_array_element, append_last);
return;
}
}
_ => {}
}
self.error_handler
.redefinition(ParserError::Redefined.spanned(key.span), o.key().span);
o.shift_remove();
}
let mut map = IndexMap::new();
let span = value.span;
self.insert(&mut map, keys, value, modify_array_element, append_last);
dst.insert(key, Value::Table(map).spanned(span));
}
}
fn parse_key_value_with_recovery(
&mut self,
) -> Result<Option<(Key, Spanned<Value>)>, Spanned<ParserError>> {
let pos = self.lexer.pos();
match self.parse_key_value() {
Ok(kv) => Ok(Some(kv)),
Err((e, key)) => {
if let Some(key) = key {
let span = key.back().unwrap().span;
self.error_handler
.handle(ParserError::IgnoringKey.spanned(span));
}
if self.lexer.pos() == pos {
Err(e)
} else {
self.error_handler.handle(e);
Ok(None)
}
}
}
}
#[allow(clippy::type_complexity)]
fn parse_key_value(
&mut self,
) -> Result<(Key, Spanned<Value>), (Spanned<ParserError>, Option<Key>)> {
let key = self.parse_key();
let eq = self.parse_exact(Token::Equals, true);
let value = self.parse_value();
let key = match key {
Ok(k) => k,
Err(e) => return Err((e, None)),
};
if let Err(e) = eq {
return Err((e, Some(key)));
}
let value = match value {
Ok(v) => v,
Err(e) => return Err((e, Some(key))),
};
Ok((key, value))
}
fn parse_key(&mut self) -> Result<Key, Spanned<ParserError>> {
let mut parts = Key::new();
loop {
if parts.len() > 0 {
if self.parse_exact(Token::Dot, false).is_err() {
break;
}
}
let Some(token) = self.lexer.peek(false) else {
break;
};
let s = match token.value {
Token::LiteralString(s) => s.as_bstr().to_string(),
Token::CookedString(s) => self.cook_string(s),
Token::Literal(l) => l.as_bstr().to_string(),
_ => break,
};
parts.push_back(s.spanned(token.span));
let _ = self.next(false);
}
if parts.is_empty() {
Err(ParserError::MissingKey.spanned(self.next_span()))
} else {
Ok(parts)
}
}
fn parse_exact(
&mut self,
token: Token<'a>,
value_context: bool,
) -> Result<Span, Spanned<ParserError>> {
let actual = match self.peek(value_context) {
Ok(t) if t.value == token => {
let _ = self.next(value_context);
return Ok(t.span);
}
Ok(t) => t.value.name(value_context),
Err(_) => "end of file",
};
let span = self.next_span();
Err(ParserError::Expected(token.name(value_context), actual).spanned(span))
}
fn last_span(&self) -> Span {
self.last_span.unwrap_or(Span { lo: 0, hi: 0 })
}
fn next_span(&mut self) -> Span {
self.lexer.peek(false).map(|v| v.span).unwrap_or_else(|| {
let hi = self.last_span().hi;
Span { lo: hi, hi }
})
}
fn skip_tree(&mut self, start: Token, end: Token) {
let mut depth = 1;
while let Ok(next) = self.next(false) {
if next.value == start {
depth += 1;
} else if next.value == end {
depth -= 1;
if depth == 0 {
return;
}
}
}
}
}

View file

@ -0,0 +1,124 @@
use std::{
borrow::Borrow,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
};
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Span {
pub lo: usize,
pub hi: usize,
}
impl Debug for Span {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.lo, self.hi)
}
}
#[derive(Copy, Clone, Eq)]
pub struct Spanned<T> {
pub span: Span,
pub value: T,
}
impl<T> Spanned<T> {
pub fn as_ref(&self) -> Spanned<&T> {
Spanned {
span: self.span,
value: &self.value,
}
}
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
Spanned {
span: self.span,
value: f(self.value),
}
}
pub fn into<U>(self) -> Spanned<U>
where
T: Into<U>,
{
Spanned {
span: self.span,
value: self.value.into(),
}
}
}
impl<T: Debug> Debug for Spanned<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)?;
write!(f, " @ {:?}", self.span)
}
}
impl<T: PartialEq> PartialEq for Spanned<T> {
fn eq(&self, other: &Self) -> bool {
self.value.eq(&other.value)
}
}
impl<T: Hash> Hash for Spanned<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state)
}
}
impl Borrow<str> for Spanned<String> {
fn borrow(&self) -> &str {
&self.value
}
}
pub trait SpannedExt: Sized {
fn spanned(self, span: Span) -> Spanned<Self>;
}
impl<T> SpannedExt for T {
fn spanned(self, span: Span) -> Spanned<Self> {
Spanned { span, value: self }
}
}
pub trait DespanExt: Sized {
type T;
fn despan(self) -> Option<Self::T>;
fn despan_into<U>(self) -> Option<U>
where
Self::T: Into<U>;
}
impl<T> DespanExt for Option<Spanned<T>> {
type T = T;
fn despan(self) -> Option<Self::T> {
self.map(|v| v.value)
}
fn despan_into<U>(self) -> Option<U>
where
Self::T: Into<U>,
{
self.map(|v| v.value.into())
}
}
pub trait SpannedResultExt1: Sized {
type T;
type E;
fn map_spanned<U, F: FnOnce(Self::T) -> U>(self, f: F) -> Result<Spanned<U>, Self::E>;
}
impl<T, E> SpannedResultExt1 for Result<Spanned<T>, E> {
type T = T;
type E = E;
fn map_spanned<U, F: FnOnce(Self::T) -> U>(self, f: F) -> Result<Spanned<U>, Self::E> {
self.map(|v| v.map(f))
}
}

View file

@ -0,0 +1,63 @@
use {
crate::toml::toml_span::Spanned,
indexmap::IndexMap,
std::{
cmp::Ordering,
fmt::{Debug, Formatter},
},
};
pub enum Value {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<Spanned<Value>>),
Table(IndexMap<Spanned<String>, Spanned<Value>>),
}
impl Value {
pub fn name(&self) -> &'static str {
match self {
Value::String(_) => "a string",
Value::Integer(_) => "an integer",
Value::Float(_) => "a float",
Value::Boolean(_) => "a boolean",
Value::Array(_) => "an array",
Value::Table(_) => "a table",
}
}
}
impl Debug for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Value::String(v) => v.fmt(f),
Value::Integer(v) => v.fmt(f),
Value::Float(v) => v.fmt(f),
Value::Boolean(v) => v.fmt(f),
Value::Array(v) => v.fmt(f),
Value::Table(v) => v.fmt(f),
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::String(v1), Value::String(v2)) => v1 == v2,
(Value::Integer(v1), Value::Integer(v2)) => v1 == v2,
(Value::Float(v1), Value::Float(v2)) => {
if v1.is_nan() && v2.is_nan() {
true
} else {
v1.total_cmp(v2) == Ordering::Equal
}
}
(Value::Boolean(v1), Value::Boolean(v2)) => v1 == v2,
(Value::Array(v1), Value::Array(v2)) => v1 == v2,
(Value::Table(v1), Value::Table(v2)) => v1 == v2,
_ => false,
}
}
}

12
toml-spec/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "toml-spec"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.197", features = ["derive"] }
serde_yaml = "0.9.32"
anyhow = "1.0.81"
indexmap = { version = "2.2.5", features = ["serde"] }
error_reporter = "1.0.0"
serde_json = { version = "1.0.114", features = ["preserve_order"] }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1936
toml-spec/spec/spec.yaml Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
# Jay TOML Config
This document describes the format of the TOML configuration of the Jay compositor.
A JSON Schema for this format is available at [spec.generated.json](./spec.generated.json). You can include this file in your editor to get auto completion.
Start at the top-level type: [Config](#types-config).
## Types

View file

@ -0,0 +1,194 @@
use {
crate::types::{
ArraySpec, Described, MapSpec, NestableTypesSpec, NumberSpec, RefOrSpec, SingleTableSpec,
StringSpec, TableSpec, TopLevelTypeSpec, VariantSpec,
},
anyhow::Result,
serde_json::{json, Map, Value},
};
pub fn generate_json_schema(
types_sorted: &[(&String, &Described<TopLevelTypeSpec>)],
) -> Result<()> {
let mut types = Map::new();
for (name, ty) in types_sorted {
types.insert(name.to_string(), create_top_level_schema(ty));
}
let json = json!({
"$id": "jay_toml_schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/Config",
"$defs": types,
});
let json = serde_json::to_string_pretty(&json).unwrap();
std::fs::write(
concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.generated.json"),
json.as_bytes(),
)?;
Ok(())
}
fn create_top_level_schema(spec: &Described<TopLevelTypeSpec>) -> Value {
match &spec.value {
TopLevelTypeSpec::Variable { variants } => {
let mut cases = vec![];
for variant in variants {
cases.push(create_variant_schema(&variant.description, &variant.value));
}
json!({
"description": spec.description,
"anyOf": cases,
})
}
TopLevelTypeSpec::Single(variant) => create_variant_schema(&spec.description, variant),
}
}
fn create_variant_schema(description: &str, spec: &VariantSpec) -> Value {
macro_rules! spec {
($v:expr) => {
match $v {
RefOrSpec::Ref { name } => return create_ref_spec(description, name),
RefOrSpec::Spec(s) => s,
}
};
}
match spec {
VariantSpec::String(ss) => {
let ss = spec!(ss);
create_string_spec(description, ss)
}
VariantSpec::Number(ns) => {
let ns = spec!(ns);
create_number_spec(description, ns)
}
VariantSpec::Boolean => create_boolean_spec(description),
VariantSpec::Array(s) => {
let s = spec!(s);
create_array_spec(description, s)
}
VariantSpec::Table(ts) => {
let ts = spec!(ts);
match ts {
TableSpec::Tagged { types } => {
let mut variants = vec![];
for (name, ty) in types {
variants.push(create_single_table_spec(
&ty.description,
&ty.value,
Some(name),
));
}
json!({
"description": description,
"anyOf": variants,
})
}
TableSpec::Single(s) => create_single_table_spec(description, s, None),
}
}
}
}
fn create_single_table_spec(
description: &str,
spec: &SingleTableSpec,
type_: Option<&str>,
) -> Value {
let mut properties = Map::new();
let mut required = vec![];
if let Some(type_) = type_ {
properties.insert("type".into(), json!({ "const": type_ }));
required.push("type".into());
}
for (key, val) in &spec.fields {
properties.insert(
key.into(),
create_nestable_type_spec(&val.description, &val.value.kind),
);
if val.value.required {
required.push(key.to_string());
}
}
json!({
"description": description,
"type": "object",
"properties": properties,
"required": required,
})
}
fn create_ref_spec(description: &str, name: &str) -> Value {
let path = format!("#/$defs/{name}");
json!({
"description": description,
"$ref": path,
})
}
fn create_nestable_type_spec(description: &str, spec: &RefOrSpec<NestableTypesSpec>) -> Value {
let spec = match spec {
RefOrSpec::Ref { name } => return create_ref_spec(description, name),
RefOrSpec::Spec(s) => s,
};
match spec {
NestableTypesSpec::String(s) => create_string_spec(description, s),
NestableTypesSpec::Number(s) => create_number_spec(description, s),
NestableTypesSpec::Boolean => create_boolean_spec(description),
NestableTypesSpec::Array(s) => create_array_spec(description, s),
NestableTypesSpec::Map(s) => create_map_spec(description, s),
}
}
fn create_map_spec(description: &str, spec: &MapSpec) -> Value {
json!({
"description": description,
"type": "object",
"additionalProperties": create_nestable_type_spec("", &spec.values),
})
}
fn create_string_spec(description: &str, spec: &StringSpec) -> Value {
let mut res = Map::new();
res.insert("type".into(), json!("string"));
res.insert("description".into(), json!(description));
if let Some(values) = &spec.values {
let strings: Vec<_> = values.iter().map(|v| &v.value.value).collect();
res.insert("enum".into(), json!(strings));
}
res.into()
}
fn create_array_spec(description: &str, spec: &ArraySpec) -> Value {
json!({
"type": "array",
"description": description,
"items": create_nestable_type_spec("", &spec.items),
})
}
fn create_number_spec(description: &str, spec: &NumberSpec) -> Value {
let ty = match spec.integer_only {
true => "integer",
false => "number",
};
let mut res = Map::new();
res.insert("type".into(), json!(ty));
res.insert("description".into(), json!(description));
if let Some(minimum) = spec.minimum {
let key = match spec.exclusive_minimum {
true => "exclusiveMinimum",
false => "minimum",
};
res.insert(key.into(), json!(minimum));
}
res.into()
}
fn create_boolean_spec(description: &str) -> Value {
json!({"type": "boolean", "description": description})
}

27
toml-spec/src/main.rs Normal file
View file

@ -0,0 +1,27 @@
use {
crate::{
json_schema::generate_json_schema,
markdown::generate_markdown,
types::{Described, TopLevelTypeSpec},
},
anyhow::Result,
indexmap::IndexMap,
};
mod json_schema;
mod markdown;
mod types;
fn parse() -> Result<IndexMap<String, Described<TopLevelTypeSpec>>> {
let file = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.yaml"))?;
Ok(serde_yaml::from_str(&file)?)
}
fn main() -> Result<()> {
let types = parse()?;
let mut types_sorted: Vec<_> = types.iter().collect();
types_sorted.sort_by_key(|t| t.0);
generate_markdown(&types_sorted)?;
generate_json_schema(&types_sorted)?;
Ok(())
}

259
toml-spec/src/markdown.rs Normal file
View file

@ -0,0 +1,259 @@
use {
crate::types::{
ArraySpec, Described, NestableTypesSpec, NumberSpec, RefOrSpec, SingleTableSpec,
StringSpec, TableSpec, TopLevelTypeSpec, VariantSpec,
},
anyhow::Result,
std::io::Write,
};
pub fn generate_markdown(types: &[(&String, &Described<TopLevelTypeSpec>)]) -> Result<()> {
const TEMPLATE: &str = include_str!("../spec/template.md");
let mut buf = vec![];
buf.extend_from_slice(TEMPLATE.as_bytes());
for (name, ty) in types {
write_top_level_type_spec(&mut buf, name, ty)?;
}
std::fs::write(
concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.generated.md"),
&buf,
)?;
Ok(())
}
fn write_top_level_type_spec(
buf: &mut Vec<u8>,
name: &str,
spec: &Described<TopLevelTypeSpec>,
) -> Result<()> {
writeln!(buf, "<a name=\"types-{name}\"></a>")?;
writeln!(buf, "### `{name}`")?;
writeln!(buf)?;
writeln!(buf, "{}", spec.description.trim())?;
writeln!(buf)?;
match &spec.value {
TopLevelTypeSpec::Variable { variants } => {
writeln!(
buf,
"Values of this type should have one of the following forms:"
)?;
writeln!(buf)?;
for variant in variants {
write!(buf, "#### ")?;
let name = match &variant.value {
VariantSpec::String(_) => "A string",
VariantSpec::Number(_) => "A number",
VariantSpec::Boolean => "A boolean",
VariantSpec::Array(_) => "An array",
VariantSpec::Table(_) => "A table",
};
writeln!(buf, "{name}")?;
writeln!(buf)?;
writeln!(buf, "{}", variant.description.trim())?;
writeln!(buf)?;
write_variant_spec(buf, &variant.value)?;
}
}
TopLevelTypeSpec::Single(variant) => {
let name = match &variant {
VariantSpec::String(_) => "strings",
VariantSpec::Number(_) => "numbers",
VariantSpec::Boolean => "booleans",
VariantSpec::Array(_) => "arrays",
VariantSpec::Table(_) => "tables",
};
writeln!(buf, "Values of this type should be {name}.")?;
writeln!(buf)?;
write_variant_spec(buf, variant)?;
}
}
writeln!(buf)?;
Ok(())
}
fn write_variant_spec(buf: &mut Vec<u8>, spec: &VariantSpec) -> Result<()> {
macro_rules! spec {
($v:expr) => {
match $v {
RefOrSpec::Ref { name } => {
writeln!(buf, "The value should be a [{name}](#types-{name}).")?;
writeln!(buf)?;
return Ok(());
}
RefOrSpec::Spec(s) => s,
}
};
}
match spec {
VariantSpec::String(ss) => {
let ss = spec!(ss);
write_string_spec(buf, ss, "")?;
}
VariantSpec::Number(ns) => {
let ns = spec!(ns);
write_number_spec(buf, ns, "")?;
}
VariantSpec::Boolean => {}
VariantSpec::Array(s) => {
let s = spec!(s);
write_array_spec(buf, s, "")?;
}
VariantSpec::Table(ts) => {
let ts = spec!(ts);
match ts {
TableSpec::Tagged { types } => {
writeln!(buf, "This table is a tagged union. The variant is determined by the `type` field. It takes one of the following values:")?;
writeln!(buf)?;
for (name, spec) in types {
writeln!(buf, "- `{name}`:")?;
writeln!(buf)?;
for line in spec.description.trim().lines() {
writeln!(buf, " {line}")?;
}
writeln!(buf)?;
write_single_table_spec(buf, &spec.value, " ")?;
}
}
TableSpec::Single(s) => {
write_single_table_spec(buf, s, "")?;
}
}
}
}
Ok(())
}
fn write_single_table_spec(buf: &mut Vec<u8>, spec: &SingleTableSpec, pad: &str) -> Result<()> {
writeln!(buf, "{pad}The table has the following fields:")?;
writeln!(buf)?;
for (name, fs) in &spec.fields {
let optional = match fs.value.required {
true => "required",
false => "optional",
};
writeln!(buf, "{pad}- `{name}` ({optional}):")?;
writeln!(buf)?;
for line in fs.description.trim().lines() {
writeln!(buf, "{pad} {line}")?;
}
writeln!(buf)?;
write!(buf, "{pad} The value of this field should be ")?;
let spec = write_nestable_type_spec(buf, &fs.value.kind, false)?;
writeln!(buf, ".")?;
writeln!(buf)?;
if let Some(spec) = spec {
let pad = format!("{pad} ");
write_nestable_type_restrictions(buf, spec, &pad)?;
}
}
Ok(())
}
fn write_nestable_type_spec<'a>(
buf: &mut Vec<u8>,
spec: &'a RefOrSpec<NestableTypesSpec>,
plural: bool,
) -> Result<Option<&'a NestableTypesSpec>> {
let spec = match spec {
RefOrSpec::Ref { name } => {
if plural {
write!(buf, "[{name}s](#types-{name})")?;
} else {
write!(buf, "a [{name}](#types-{name})")?;
}
return Ok(None);
}
RefOrSpec::Spec(s) => s,
};
let name = match (spec, plural) {
(NestableTypesSpec::String(_), false) => "a string",
(NestableTypesSpec::String(_), true) => "strings",
(NestableTypesSpec::Number(_), false) => "a number",
(NestableTypesSpec::Number(_), true) => "numbers",
(NestableTypesSpec::Boolean, false) => "a boolean",
(NestableTypesSpec::Boolean, true) => "booleans",
(NestableTypesSpec::Map(s), _) => {
let name = match plural {
true => "tables",
false => "a table",
};
write!(buf, "{name} whose values are ")?;
return write_nestable_type_spec(buf, &s.values, true);
}
(NestableTypesSpec::Array(s), _) => {
let name = match plural {
true => "arrays",
false => "an array",
};
write!(buf, "{name} of ")?;
return write_nestable_type_spec(buf, &s.items, true);
}
};
write!(buf, "{name}")?;
Ok(Some(spec))
}
fn write_nestable_type_restrictions(
buf: &mut Vec<u8>,
spec: &NestableTypesSpec,
pad: &str,
) -> Result<()> {
match spec {
NestableTypesSpec::String(s) => write_string_spec(buf, s, pad),
NestableTypesSpec::Number(s) => write_number_spec(buf, s, pad),
NestableTypesSpec::Boolean => Ok(()),
NestableTypesSpec::Array(_) => Ok(()),
NestableTypesSpec::Map(_) => Ok(()),
}
}
fn write_string_spec(buf: &mut Vec<u8>, spec: &StringSpec, pad: &str) -> Result<()> {
if let Some(values) = &spec.values {
writeln!(
buf,
"{pad}The string should have one of the following values:"
)?;
writeln!(buf)?;
for value in values {
writeln!(buf, "{pad}- `{}`:", value.value.value)?;
writeln!(buf)?;
for line in value.description.lines() {
writeln!(buf, "{pad} {line}")?;
}
writeln!(buf)?;
}
writeln!(buf)?;
}
Ok(())
}
fn write_array_spec(buf: &mut Vec<u8>, spec: &ArraySpec, pad: &str) -> Result<()> {
write!(buf, "{pad}Each element of this array should be ")?;
let spec = write_nestable_type_spec(buf, &spec.items, false)?;
writeln!(buf, ".")?;
writeln!(buf)?;
if let Some(spec) = spec {
write_nestable_type_restrictions(buf, spec, pad)?;
}
Ok(())
}
fn write_number_spec(buf: &mut Vec<u8>, spec: &NumberSpec, pad: &str) -> Result<()> {
if spec.integer_only {
writeln!(buf, "{pad}The numbers should be integers.")?;
writeln!(buf)?;
}
if let Some(minimum) = spec.minimum {
let greater = match spec.exclusive_minimum {
true => "strictly greater than",
false => "greater than or equal to",
};
writeln!(buf, "{pad}The numbers should be {greater} {minimum}.")?;
writeln!(buf)?;
}
Ok(())
}

184
toml-spec/src/types.rs Normal file
View file

@ -0,0 +1,184 @@
use {
error_reporter::Report,
indexmap::IndexMap,
serde::{
de::{DeserializeOwned, Error},
Deserialize, Deserializer,
},
};
#[derive(Debug, Deserialize)]
pub struct Described<T> {
pub description: String,
#[serde(flatten)]
pub value: T,
}
#[derive(Debug)]
pub enum TopLevelTypeSpec {
Variable {
variants: Vec<Described<VariantSpec>>,
},
Single(VariantSpec),
}
#[derive(Debug)]
pub enum TableSpec {
Tagged {
types: IndexMap<String, Described<SingleTableSpec>>,
},
Single(SingleTableSpec),
}
#[derive(Debug, Deserialize)]
pub struct SingleTableSpec {
pub fields: IndexMap<String, Described<TableFieldSpec>>,
}
#[derive(Debug, Deserialize)]
pub struct TableFieldSpec {
pub required: bool,
#[serde(flatten)]
pub kind: RefOrSpec<NestableTypesSpec>,
}
#[derive(Debug)]
pub enum RefOrSpec<T> {
Ref { name: String },
Spec(T),
}
#[derive(Debug, Deserialize)]
pub struct StringSpec {
pub values: Option<Vec<Described<StringSpecValue>>>,
}
#[derive(Debug, Deserialize)]
pub struct StringSpecValue {
pub value: String,
}
#[derive(Debug, Deserialize)]
pub struct NumberSpec {
#[serde(default)]
pub integer_only: bool,
pub minimum: Option<f64>,
#[serde(default)]
pub exclusive_minimum: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "kind")]
pub enum VariantSpec {
String(RefOrSpec<StringSpec>),
Number(RefOrSpec<NumberSpec>),
Boolean,
Array(RefOrSpec<ArraySpec>),
Table(RefOrSpec<TableSpec>),
}
#[derive(Debug, Deserialize)]
pub struct ArraySpec {
pub items: Box<RefOrSpec<NestableTypesSpec>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "kind")]
pub enum NestableTypesSpec {
String(StringSpec),
Number(NumberSpec),
Boolean,
Array(ArraySpec),
Map(MapSpec),
}
#[derive(Debug, Deserialize)]
pub struct MapSpec {
pub values: Box<RefOrSpec<NestableTypesSpec>>,
}
impl<'de> Deserialize<'de> for TopLevelTypeSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let v = serde_yaml::Value::deserialize(deserializer)?;
#[derive(Debug, Deserialize)]
struct Variable {
variants: Vec<Described<VariantSpec>>,
}
let variable = Variable::deserialize(&v);
let single = VariantSpec::deserialize(&v);
let res = match (variable, single) {
(Ok(variable), _) => Self::Variable {
variants: variable.variants,
},
(_, Ok(single)) => Self::Single(single),
(Err(e1), Err(e2)) => {
return Err(Error::custom(format!(
"spec must define either variants or a single variant. failures: {} ----- {}",
Report::new(e1),
Report::new(e2)
)))
}
};
Ok(res)
}
}
impl<'de> Deserialize<'de> for TableSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let v = serde_yaml::Value::deserialize(deserializer)?;
#[derive(Debug, Deserialize)]
struct Tagged {
types: IndexMap<String, Described<SingleTableSpec>>,
}
let tagged = Tagged::deserialize(&v);
let single = SingleTableSpec::deserialize(&v);
let res = match (tagged, single) {
(Ok(tagged), _) => Self::Tagged {
types: tagged.types,
},
(_, Ok(single)) => Self::Single(single),
(Err(e1), Err(e2)) => {
return Err(Error::custom(format!(
"spec must define either types or fields. failures: {} ----- {}",
Report::new(e1),
Report::new(e2)
)))
}
};
Ok(res)
}
}
impl<'de, U: DeserializeOwned> Deserialize<'de> for RefOrSpec<U> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let v = serde_yaml::Value::deserialize(deserializer)?;
#[derive(Debug, Deserialize)]
struct Ref {
#[serde(rename = "ref")]
name: String,
}
let name = Ref::deserialize(&v);
let single = U::deserialize(&v);
let res = match (name, single) {
(Ok(name), _) => Self::Ref { name: name.name },
(_, Ok(single)) => Self::Spec(single),
(Err(e1), Err(e2)) => {
return Err(Error::custom(format!(
"spec must define either a ref or a spec. failures: {} ----- {}",
Report::new(e1),
Report::new(e2)
)))
}
};
Ok(res)
}
}