1
0
Fork 0
forked from wry/wry

workspace: move crates under crates

This commit is contained in:
kossLAN 2026-05-29 18:55:59 -04:00
parent 0016bc8cf0
commit 6393fdf3c0
No known key found for this signature in database
354 changed files with 102 additions and 102 deletions

View file

@ -0,0 +1,121 @@
mod context;
pub mod error;
mod extractor;
mod keycodes;
mod parser;
mod parsers;
mod spanned;
use {
crate::{
config::{
context::Context,
parsers::{
config::{ConfigParser, ConfigParserError},
},
},
},
ahash::AHashMap,
std::{
cell::RefCell,
error::Error,
},
thiserror::Error,
jay_toml::toml_parser,
};
pub use jay_config_schema::{
Action, AnimationCurveConfig, Animations, ClientMatch, ClientRule, ColorManagement, Config,
ConfigConnector, ConfigDrmDevice, ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Float,
FocusHistory, GenericMatch, Input, InputMatch, InputMode, Libei, MatchExactly, Mode,
NamedAction, Output, OutputMatch, RepeatRate, Shortcut, SimpleCommand, SimpleIm, Status,
Tearing, Theme, UiDrag, Vrr, WindowMatch, WindowRule, Xwayland,
};
#[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],
mark_names: &RefCell<AHashMap<String, u32>>,
handle_error: F,
) -> Option<Config>
where
F: FnOnce(&dyn Error),
{
let cx = Context {
input,
used: Default::default(),
mark_names,
};
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, &Default::default(), |_| ()).unwrap();
}
#[test]
fn custom_animation_curve_parses() {
let input = b"
[animations]
curve = [0.25, 0.1, 0.25, 1.0]
";
let config = parse_config(input, &Default::default(), |_| ()).unwrap();
assert_eq!(
config.animations.curve,
Some(AnimationCurveConfig::CubicBezier([0.25, 0.1, 0.25, 1.0]))
);
}
#[test]
fn animation_style_parses() {
let input = b"
[animations]
style = \"plain\"
";
let config = parse_config(input, &Default::default(), |_| ()).unwrap();
assert_eq!(config.animations.style.as_deref(), Some("plain"));
}

View file

@ -0,0 +1,66 @@
use {
crate::{
config::error::SpannedError,
jay_toml::{
toml_parser::{ErrorHandler, ParserError},
toml_span::{Span, Spanned},
},
},
ahash::{AHashMap, AHashSet},
error_reporter::Report,
std::{cell::RefCell, convert::Infallible, error::Error},
};
pub struct Context<'a> {
pub input: &'a [u8],
pub used: RefCell<Used>,
pub mark_names: &'a RefCell<AHashMap<String, u32>>,
}
#[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 ErrorHandler for Context<'_> {
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 {
jay_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<E: Error> Display for SpannedError<'_, 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 {line_num}, column {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<E: Error> Error for SpannedError<'_, 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,283 @@
use {
crate::{
config::context::Context,
jay_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 span(&self) -> Span {
self.span
}
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 Drop for Extractor<'_> {
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) => {
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) => {
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,)*);
#[expect(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,);

View file

@ -0,0 +1,520 @@
use phf::phf_map;
pub static KEYCODES: phf::Map<&'static str, u32> = phf_map! {
"esc" => 1,
"1" => 2,
"2" => 3,
"3" => 4,
"4" => 5,
"5" => 6,
"6" => 7,
"7" => 8,
"8" => 9,
"9" => 10,
"0" => 11,
"minus" => 12,
"equal" => 13,
"backspace" => 14,
"tab" => 15,
"q" => 16,
"w" => 17,
"e" => 18,
"r" => 19,
"t" => 20,
"y" => 21,
"u" => 22,
"i" => 23,
"o" => 24,
"p" => 25,
"leftbrace" => 26,
"rightbrace" => 27,
"enter" => 28,
"leftctrl" => 29,
"a" => 30,
"s" => 31,
"d" => 32,
"f" => 33,
"g" => 34,
"h" => 35,
"j" => 36,
"k" => 37,
"l" => 38,
"semicolon" => 39,
"apostrophe" => 40,
"grave" => 41,
"leftshift" => 42,
"backslash" => 43,
"z" => 44,
"x" => 45,
"c" => 46,
"v" => 47,
"b" => 48,
"n" => 49,
"m" => 50,
"comma" => 51,
"dot" => 52,
"slash" => 53,
"rightshift" => 54,
"kpasterisk" => 55,
"leftalt" => 56,
"space" => 57,
"capslock" => 58,
"f1" => 59,
"f2" => 60,
"f3" => 61,
"f4" => 62,
"f5" => 63,
"f6" => 64,
"f7" => 65,
"f8" => 66,
"f9" => 67,
"f10" => 68,
"numlock" => 69,
"scrolllock" => 70,
"kp7" => 71,
"kp8" => 72,
"kp9" => 73,
"kpminus" => 74,
"kp4" => 75,
"kp5" => 76,
"kp6" => 77,
"kpplus" => 78,
"kp1" => 79,
"kp2" => 80,
"kp3" => 81,
"kp0" => 82,
"kpdot" => 83,
"zenkakuhankaku" => 85,
"102nd" => 86,
"f11" => 87,
"f12" => 88,
"ro" => 89,
"katakana" => 90,
"hiragana" => 91,
"henkan" => 92,
"katakanahiragana" => 93,
"muhenkan" => 94,
"kpjpcomma" => 95,
"kpenter" => 96,
"rightctrl" => 97,
"kpslash" => 98,
"sysrq" => 99,
"rightalt" => 100,
"linefeed" => 101,
"home" => 102,
"up" => 103,
"pageup" => 104,
"left" => 105,
"right" => 106,
"end" => 107,
"down" => 108,
"pagedown" => 109,
"insert" => 110,
"delete" => 111,
"macro" => 112,
"mute" => 113,
"volumedown" => 114,
"volumeup" => 115,
"power" => 116,
"kpequal" => 117,
"kpplusminus" => 118,
"pause" => 119,
"scale" => 120,
"kpcomma" => 121,
"hangeul" => 122,
"hanguel" => 122,
"hanja" => 123,
"yen" => 124,
"leftmeta" => 125,
"rightmeta" => 126,
"compose" => 127,
"stop" => 128,
"again" => 129,
"props" => 130,
"undo" => 131,
"front" => 132,
"copy" => 133,
"open" => 134,
"paste" => 135,
"find" => 136,
"cut" => 137,
"help" => 138,
"menu" => 139,
"calc" => 140,
"setup" => 141,
"sleep" => 142,
"wakeup" => 143,
"file" => 144,
"sendfile" => 145,
"deletefile" => 146,
"xfer" => 147,
"prog1" => 148,
"prog2" => 149,
"www" => 150,
"msdos" => 151,
"coffee" => 152,
"screenlock" => 152,
"rotate_display" => 153,
"direction" => 153,
"cyclewindows" => 154,
"mail" => 155,
"bookmarks" => 156,
"computer" => 157,
"back" => 158,
"forward" => 159,
"closecd" => 160,
"ejectcd" => 161,
"ejectclosecd" => 162,
"nextsong" => 163,
"playpause" => 164,
"previoussong" => 165,
"stopcd" => 166,
"record" => 167,
"rewind" => 168,
"phone" => 169,
"iso" => 170,
"config" => 171,
"homepage" => 172,
"refresh" => 173,
"exit" => 174,
"move" => 175,
"edit" => 176,
"scrollup" => 177,
"scrolldown" => 178,
"kpleftparen" => 179,
"kprightparen" => 180,
"new" => 181,
"redo" => 182,
"f13" => 183,
"f14" => 184,
"f15" => 185,
"f16" => 186,
"f17" => 187,
"f18" => 188,
"f19" => 189,
"f20" => 190,
"f21" => 191,
"f22" => 192,
"f23" => 193,
"f24" => 194,
"playcd" => 200,
"pausecd" => 201,
"prog3" => 202,
"prog4" => 203,
"all_applications" => 204,
"dashboard" => 204,
"suspend" => 205,
"close" => 206,
"play" => 207,
"fastforward" => 208,
"bassboost" => 209,
"print" => 210,
"hp" => 211,
"camera" => 212,
"sound" => 213,
"question" => 214,
"email" => 215,
"chat" => 216,
"search" => 217,
"connect" => 218,
"finance" => 219,
"sport" => 220,
"shop" => 221,
"alterase" => 222,
"cancel" => 223,
"brightnessdown" => 224,
"brightnessup" => 225,
"media" => 226,
"switchvideomode" => 227,
"kbdillumtoggle" => 228,
"kbdillumdown" => 229,
"kbdillumup" => 230,
"send" => 231,
"reply" => 232,
"forwardmail" => 233,
"save" => 234,
"documents" => 235,
"battery" => 236,
"bluetooth" => 237,
"wlan" => 238,
"uwb" => 239,
"unknown" => 240,
"video_next" => 241,
"video_prev" => 242,
"brightness_cycle" => 243,
"brightness_auto" => 244,
"brightness_zero" => 244,
"display_off" => 245,
"wwan" => 246,
"wimax" => 246,
"rfkill" => 247,
"micmute" => 248,
"ok" => 0x160,
"select" => 0x161,
"goto" => 0x162,
"clear" => 0x163,
"power2" => 0x164,
"option" => 0x165,
"info" => 0x166,
"time" => 0x167,
"vendor" => 0x168,
"archive" => 0x169,
"program" => 0x16a,
"channel" => 0x16b,
"favorites" => 0x16c,
"epg" => 0x16d,
"pvr" => 0x16e,
"mhp" => 0x16f,
"language" => 0x170,
"title" => 0x171,
"subtitle" => 0x172,
"angle" => 0x173,
"full_screen" => 0x174,
"zoom" => 0x174,
"mode" => 0x175,
"keyboard" => 0x176,
"aspect_ratio" => 0x177,
"screen" => 0x177,
"pc" => 0x178,
"tv" => 0x179,
"tv2" => 0x17a,
"vcr" => 0x17b,
"vcr2" => 0x17c,
"sat" => 0x17d,
"sat2" => 0x17e,
"cd" => 0x17f,
"tape" => 0x180,
"radio" => 0x181,
"tuner" => 0x182,
"player" => 0x183,
"text" => 0x184,
"dvd" => 0x185,
"aux" => 0x186,
"mp3" => 0x187,
"audio" => 0x188,
"video" => 0x189,
"directory" => 0x18a,
"list" => 0x18b,
"memo" => 0x18c,
"calendar" => 0x18d,
"red" => 0x18e,
"green" => 0x18f,
"yellow" => 0x190,
"blue" => 0x191,
"channelup" => 0x192,
"channeldown" => 0x193,
"first" => 0x194,
"last" => 0x195,
"ab" => 0x196,
"next" => 0x197,
"restart" => 0x198,
"slow" => 0x199,
"shuffle" => 0x19a,
"break" => 0x19b,
"previous" => 0x19c,
"digits" => 0x19d,
"teen" => 0x19e,
"twen" => 0x19f,
"videophone" => 0x1a0,
"games" => 0x1a1,
"zoomin" => 0x1a2,
"zoomout" => 0x1a3,
"zoomreset" => 0x1a4,
"wordprocessor" => 0x1a5,
"editor" => 0x1a6,
"spreadsheet" => 0x1a7,
"graphicseditor" => 0x1a8,
"presentation" => 0x1a9,
"database" => 0x1aa,
"news" => 0x1ab,
"voicemail" => 0x1ac,
"addressbook" => 0x1ad,
"messenger" => 0x1ae,
"displaytoggle" => 0x1af,
"brightness_toggle" => 0x1af,
"spellcheck" => 0x1b0,
"logoff" => 0x1b1,
"dollar" => 0x1b2,
"euro" => 0x1b3,
"frameback" => 0x1b4,
"frameforward" => 0x1b5,
"context_menu" => 0x1b6,
"media_repeat" => 0x1b7,
"10channelsup" => 0x1b8,
"10channelsdown" => 0x1b9,
"images" => 0x1ba,
"notification_center" => 0x1bc,
"pickup_phone" => 0x1bd,
"hangup_phone" => 0x1be,
"del_eol" => 0x1c0,
"del_eos" => 0x1c1,
"ins_line" => 0x1c2,
"del_line" => 0x1c3,
"fn" => 0x1d0,
"fn_esc" => 0x1d1,
"fn_f1" => 0x1d2,
"fn_f2" => 0x1d3,
"fn_f3" => 0x1d4,
"fn_f4" => 0x1d5,
"fn_f5" => 0x1d6,
"fn_f6" => 0x1d7,
"fn_f7" => 0x1d8,
"fn_f8" => 0x1d9,
"fn_f9" => 0x1da,
"fn_f10" => 0x1db,
"fn_f11" => 0x1dc,
"fn_f12" => 0x1dd,
"fn_1" => 0x1de,
"fn_2" => 0x1df,
"fn_d" => 0x1e0,
"fn_e" => 0x1e1,
"fn_f" => 0x1e2,
"fn_s" => 0x1e3,
"fn_b" => 0x1e4,
"fn_right_shift" => 0x1e5,
"brl_dot1" => 0x1f1,
"brl_dot2" => 0x1f2,
"brl_dot3" => 0x1f3,
"brl_dot4" => 0x1f4,
"brl_dot5" => 0x1f5,
"brl_dot6" => 0x1f6,
"brl_dot7" => 0x1f7,
"brl_dot8" => 0x1f8,
"brl_dot9" => 0x1f9,
"brl_dot10" => 0x1fa,
"numeric_0" => 0x200,
"numeric_1" => 0x201,
"numeric_2" => 0x202,
"numeric_3" => 0x203,
"numeric_4" => 0x204,
"numeric_5" => 0x205,
"numeric_6" => 0x206,
"numeric_7" => 0x207,
"numeric_8" => 0x208,
"numeric_9" => 0x209,
"numeric_star" => 0x20a,
"numeric_pound" => 0x20b,
"numeric_a" => 0x20c,
"numeric_b" => 0x20d,
"numeric_c" => 0x20e,
"numeric_d" => 0x20f,
"camera_focus" => 0x210,
"wps_button" => 0x211,
"touchpad_toggle" => 0x212,
"touchpad_on" => 0x213,
"touchpad_off" => 0x214,
"camera_zoomin" => 0x215,
"camera_zoomout" => 0x216,
"camera_up" => 0x217,
"camera_down" => 0x218,
"camera_left" => 0x219,
"camera_right" => 0x21a,
"attendant_on" => 0x21b,
"attendant_off" => 0x21c,
"attendant_toggle" => 0x21d,
"lights_toggle" => 0x21e,
"als_toggle" => 0x230,
"rotate_lock_toggle" => 0x231,
"refresh_rate_toggle" => 0x232,
"buttonconfig" => 0x240,
"taskmanager" => 0x241,
"journal" => 0x242,
"controlpanel" => 0x243,
"appselect" => 0x244,
"screensaver" => 0x245,
"voicecommand" => 0x246,
"assistant" => 0x247,
"kbd_layout_next" => 0x248,
"emoji_picker" => 0x249,
"dictate" => 0x24a,
"camera_access_enable" => 0x24b,
"camera_access_disable" => 0x24c,
"camera_access_toggle" => 0x24d,
"accessibility" => 0x24e,
"do_not_disturb" => 0x24f,
"brightness_min" => 0x250,
"brightness_max" => 0x251,
"kbdinputassist_prev" => 0x260,
"kbdinputassist_next" => 0x261,
"kbdinputassist_prevgroup" => 0x262,
"kbdinputassist_nextgroup" => 0x263,
"kbdinputassist_accept" => 0x264,
"kbdinputassist_cancel" => 0x265,
"right_up" => 0x266,
"right_down" => 0x267,
"left_up" => 0x268,
"left_down" => 0x269,
"root_menu" => 0x26a,
"media_top_menu" => 0x26b,
"numeric_11" => 0x26c,
"numeric_12" => 0x26d,
"audio_desc" => 0x26e,
"3d_mode" => 0x26f,
"next_favorite" => 0x270,
"stop_record" => 0x271,
"pause_record" => 0x272,
"vod" => 0x273,
"unmute" => 0x274,
"fastreverse" => 0x275,
"slowreverse" => 0x276,
"data" => 0x277,
"onscreen_keyboard" => 0x278,
"privacy_screen_toggle" => 0x279,
"selective_screenshot" => 0x27a,
"next_element" => 0x27b,
"previous_element" => 0x27c,
"autopilot_engage_toggle" => 0x27d,
"mark_waypoint" => 0x27e,
"sos" => 0x27f,
"nav_chart" => 0x280,
"fishing_chart" => 0x281,
"single_range_radar" => 0x282,
"dual_range_radar" => 0x283,
"radar_overlay" => 0x284,
"traditional_sonar" => 0x285,
"clearvu_sonar" => 0x286,
"sidevu_sonar" => 0x287,
"nav_info" => 0x288,
"brightness_menu" => 0x289,
"macro1" => 0x290,
"macro2" => 0x291,
"macro3" => 0x292,
"macro4" => 0x293,
"macro5" => 0x294,
"macro6" => 0x295,
"macro7" => 0x296,
"macro8" => 0x297,
"macro9" => 0x298,
"macro10" => 0x299,
"macro11" => 0x29a,
"macro12" => 0x29b,
"macro13" => 0x29c,
"macro14" => 0x29d,
"macro15" => 0x29e,
"macro16" => 0x29f,
"macro17" => 0x2a0,
"macro18" => 0x2a1,
"macro19" => 0x2a2,
"macro20" => 0x2a3,
"macro21" => 0x2a4,
"macro22" => 0x2a5,
"macro23" => 0x2a6,
"macro24" => 0x2a7,
"macro25" => 0x2a8,
"macro26" => 0x2a9,
"macro27" => 0x2aa,
"macro28" => 0x2ab,
"macro29" => 0x2ac,
"macro30" => 0x2ad,
"macro_record_start" => 0x2b0,
"macro_record_stop" => 0x2b1,
"macro_preset_cycle" => 0x2b2,
"macro_preset1" => 0x2b3,
"macro_preset2" => 0x2b4,
"macro_preset3" => 0x2b5,
"kbd_lcd_menu1" => 0x2b8,
"kbd_lcd_menu2" => 0x2b9,
"kbd_lcd_menu3" => 0x2ba,
"kbd_lcd_menu4" => 0x2bb,
"kbd_lcd_menu5" => 0x2bc,
};

View file

@ -0,0 +1,3 @@
pub use jay_toml::value_parser::{
DataType, ParseResult, Parser, UnexpectedDataType,
};

View file

@ -0,0 +1,73 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::toml_span::Span,
},
thiserror::Error,
};
pub mod action;
mod actions;
mod animations;
mod clean_logs_older_than;
mod client_match;
mod client_rule;
mod color;
pub mod color_management;
pub mod config;
mod connector;
mod connector_match;
mod content_type;
mod drm_device;
mod drm_device_match;
mod env;
pub mod exec;
mod fallback_output_mode;
pub mod float;
pub mod focus_history;
mod format;
mod gfx_api;
mod idle;
mod input;
mod input_match;
pub mod input_mode;
pub mod keymap;
mod libei;
mod log_level;
pub mod mark_id;
mod mode;
pub mod modified_keysym;
mod output;
mod output_match;
mod repeat_rate;
pub mod shortcuts;
mod simple_im;
mod status;
mod tearing;
mod theme;
mod tile_state;
mod ui_drag;
mod vrr;
mod window_match;
mod window_rule;
mod window_type;
mod workspace_display_order;
mod xwayland;
#[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,588 @@
use {
crate::{
config::{
Action, SimpleCommand,
context::Context,
extractor::{Extractor, ExtractorError, arr, bol, n32, opt, s32, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
StringParser, StringParserError,
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},
mark_id::{MarkIdParser, MarkIdParserError},
output::{OutputParser, OutputParserError},
output_match::{OutputMatchParser, OutputMatchParserError},
repeat_rate::{RepeatRateParser, RepeatRateParserError},
status::{StatusParser, StatusParserError},
theme::{ThemeParser, ThemeParserError},
},
spanned::SpannedErrorExt,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::{
Direction, get_workspace,
input::{LayerDirection, Timeline},
},
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(#[source] ExecParserError),
#[error("Could not parse the configure-connector action")]
ConfigureConnector(#[source] ConnectorParserError),
#[error("Could not parse the configure-input action")]
ConfigureInput(#[source] InputParserError),
#[error("Could not parse the configure-output action")]
ConfigureOutput(#[source] OutputParserError),
#[error("Could not parse the environment variables")]
Env(#[source] EnvParserError),
#[error("Could not parse a set-keymap action")]
SetKeymap(#[source] KeymapParserError),
#[error("Could not parse a set-status action")]
Status(#[source] StatusParserError),
#[error("Could not parse a set-theme action")]
Theme(#[source] ThemeParserError),
#[error("Could not parse a set-log-level action")]
SetLogLevel(#[source] LogLevelParserError),
#[error("Could not parse a set-gfx-api action")]
GfxApi(#[source] GfxApiParserError),
#[error("Could not parse a configure-drm-device action")]
DrmDevice(#[source] DrmDeviceParserError),
#[error("Could not parse a set-render-device action")]
SetRenderDevice(#[source] DrmDeviceMatchParserError),
#[error("Could not parse a configure-idle action")]
ConfigureIdle(#[source] IdleParserError),
#[error("Could not parse a move-to-output action")]
MoveToOutput(#[source] OutputMatchParserError),
#[error("Could not parse a set-repeat-rate action")]
RepeatRate(#[source] RepeatRateParserError),
#[error("Could not parse a create-mark action")]
CreateMark(#[source] MarkIdParserError),
#[error("Could not parse a jump-to-mark action")]
JumpToMark(#[source] MarkIdParserError),
#[error("Could not parse a copy-mark action")]
CopyMark(#[source] MarkIdParserError),
#[error("Could not parse a show-workspace action")]
ShowWorkspace(#[source] OutputMatchParserError),
#[error("Unknown direction {0}")]
UnknownDirection(String),
#[error("Exactly one of `output` or `direction` must be specified")]
OutputAndDirectionMutuallyExclusive,
}
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::*};
if let Some(name) = string.strip_prefix("$") {
return Ok(Action::NamedAction {
name: name.to_string(),
});
}
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),
"toggle-fullscreen" => ToggleFullscreen,
"enter-fullscreen" => SetFullscreen(true),
"exit-fullscreen" => SetFullscreen(false),
"focus-parent" => FocusParent,
"close" => Close,
"disable-pointer-constraint" => DisablePointerConstraint,
"toggle-floating" => ToggleFloating,
"float" => SetFloating(true),
"tile" => SetFloating(false),
"quit" => Quit,
"reload-config-toml" => ReloadConfigToml,
"none" => None,
"forward" => Forward(true),
"consume" => Forward(false),
"enable-window-management" => EnableWindowManagement(true),
"disable-window-management" => EnableWindowManagement(false),
"enable-float-above-fullscreen" => SetFloatAboveFullscreen(true),
"disable-float-above-fullscreen" => SetFloatAboveFullscreen(false),
"toggle-float-above-fullscreen" => ToggleFloatAboveFullscreen,
"pin-float" => SetFloatPinned(true),
"unpin-float" => SetFloatPinned(false),
"toggle-float-pinned" => ToggleFloatPinned,
"kill-client" => KillClient,
"show-bar" => ShowBar(true),
"hide-bar" => ShowBar(false),
"toggle-bar" => ToggleBar,
"show-titles" => ShowTitles(true),
"hide-titles" => ShowTitles(false),
"toggle-titles" => ToggleTitles,
"float-titles" => FloatTitles(true),
"unfloat-titles" => FloatTitles(false),
"toggle-float-titles" => ToggleFloatTitles,
"focus-prev" => FocusHistory(Timeline::Older),
"focus-next" => FocusHistory(Timeline::Newer),
"focus-below" => FocusLayerRel(LayerDirection::Below),
"focus-above" => FocusLayerRel(LayerDirection::Above),
"focus-tiles" => FocusTiles,
"toggle-float-focus" => ToggleFocusFloatTiled,
"create-mark" => CreateMark,
"jump-to-mark" => JumpToMark,
"clear-modes" => PopMode(false),
"pop-mode" => PopMode(true),
"enable-simple-im" => EnableSimpleIm(true),
"disable-simple-im" => EnableSimpleIm(false),
"toggle-simple-im-enabled" => ToggleSimpleImEnabled,
"reload-simple-im" => ReloadSimpleIm,
"enable-unicode-input" => EnableUnicodeInput,
"warp-mouse-to-focus" => WarpMouseToFocus,
"toggle-tab" => ToggleTab,
"make-group-h" => MakeGroupH,
"make-group-v" => MakeGroupV,
"make-group-tab" => MakeGroupTab,
"change-group-opposite" => ChangeGroupOpposite,
"equalize" => Equalize,
"equalize-recursive" => EqualizeRecursive,
"move-tab-left" => MoveTabLeft,
"move-tab-right" => MoveTabRight,
"enable-autotile" => SetAutotile(true),
"disable-autotile" => SetAutotile(false),
"toggle-autotile" => ToggleAutotile,
_ => {
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))
.map_spanned_err(ActionParserError::Exec)?;
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, output) = ext.extract((str("name"), opt(val("output"))))?;
let name = name.value.to_string();
let output = output
.map(|o| {
o.parse_map(&mut OutputMatchParser(self.0))
.map_spanned_err(ActionParserError::ShowWorkspace)
})
.transpose()?;
Ok(Action::ShowWorkspace { name, output })
}
fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let name = ext.extract(str("name"))?.value.to_string();
Ok(Action::MoveToWorkspace { name })
}
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let con = ext
.extract(val("connector"))?
.parse_map(&mut ConnectorParser(self.0))
.map_spanned_err(ActionParserError::ConfigureConnector)?;
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,
is_inputs_array: false,
})
.map_spanned_err(ActionParserError::ConfigureInput)?;
Ok(Action::ConfigureInput {
input: Box::new(input),
})
}
fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let idle = ext
.extract(val("idle"))?
.parse_map(&mut IdleParser(self.0))
.map_spanned_err(ActionParserError::ConfigureIdle)?;
Ok(Action::ConfigureIdle {
idle: idle.timeout,
grace_period: idle.grace_period,
})
}
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,
})
.map_spanned_err(ActionParserError::ConfigureOutput)?;
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)
.map_spanned_err(ActionParserError::Env)?;
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,
})
.map_spanned_err(ActionParserError::SetKeymap)?;
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))
.map_spanned_err(ActionParserError::Status)?,
),
};
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))
.map_spanned_err(ActionParserError::Theme)?;
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)
.map_spanned_err(ActionParserError::SetLogLevel)?;
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)
.map_spanned_err(ActionParserError::GfxApi)?;
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))
.map_spanned_err(ActionParserError::SetRenderDevice)?;
Ok(Action::SetRenderDevice { dev: Box::new(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,
})
.map_spanned_err(ActionParserError::DrmDevice)?;
Ok(Action::ConfigureDrmDevice { dev })
}
fn parse_direction(v: Spanned<&str>) -> Result<Direction, Spanned<ActionParserError>> {
use Direction::*;
match v.value {
"left" => Ok(Left),
"right" => Ok(Right),
"up" => Ok(Up),
"down" => Ok(Down),
_ => Err(ActionParserError::UnknownDirection(v.value.to_string()).spanned(v.span)),
}
}
fn parse_move_to_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (ws, output_val, direction_val) = ext.extract((
opt(str("workspace")),
opt(val("output")),
opt(str("direction")),
))?;
// Validate that exactly one of output or direction is specified
if output_val.is_some() == direction_val.is_some() {
return Err(ActionParserError::OutputAndDirectionMutuallyExclusive.spanned(ext.span()));
}
let output = output_val
.map(|v| {
v.parse(&mut OutputMatchParser(self.0))
.map_spanned_err(ActionParserError::MoveToOutput)
})
.transpose()?;
let direction = direction_val.map(Self::parse_direction).transpose()?;
Ok(Action::MoveToOutput {
workspace: ws.despan().map(get_workspace),
output,
direction,
})
}
fn parse_set_repeat_rate(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let rate = ext
.extract(val("rate"))?
.parse_map(&mut RepeatRateParser(self.0))
.map_spanned_err(ActionParserError::RepeatRate)?;
Ok(Action::SetRepeatRate { rate })
}
fn parse_undefine_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name,) = ext.extract((str("name"),))?;
Ok(Action::UndefineAction {
name: name.value.to_string(),
})
}
fn parse_define_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name, action) = ext.extract((str("name"), val("action")))?;
Ok(Action::DefineAction {
name: name.value.to_string(),
action: Box::new(action.parse(&mut ActionParser(self.0))?),
})
}
fn parse_named_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name,) = ext.extract((str("name"),))?;
Ok(Action::NamedAction {
name: name.value.to_string(),
})
}
fn parse_create_mark(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (id,) = ext.extract((opt(val("id")),))?;
let Some(id) = id else {
return Ok(Action::SimpleCommand {
cmd: SimpleCommand::CreateMark,
});
};
let id = id
.parse(&mut MarkIdParser(self.0))
.map_spanned_err(ActionParserError::CreateMark)?;
Ok(Action::CreateMark(id))
}
fn parse_jump_to_mark(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (id,) = ext.extract((opt(val("id")),))?;
let Some(id) = id else {
return Ok(Action::SimpleCommand {
cmd: SimpleCommand::JumpToMark,
});
};
let id = id
.parse(&mut MarkIdParser(self.0))
.map_spanned_err(ActionParserError::JumpToMark)?;
Ok(Action::JumpToMark(id))
}
fn parse_copy_mark(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (src, dst) = ext.extract((val("src"), val("dst")))?;
let src = src
.parse(&mut MarkIdParser(self.0))
.map_spanned_err(ActionParserError::CopyMark)?;
let dst = dst
.parse(&mut MarkIdParser(self.0))
.map_spanned_err(ActionParserError::CopyMark)?;
Ok(Action::CopyMark(src, dst))
}
fn parse_push_mode(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name,) = ext.extract((str("name"),))?;
Ok(Action::SetMode {
name: name.value.to_string(),
latch: false,
})
}
fn parse_latch_mode(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name,) = ext.extract((str("name"),))?;
Ok(Action::SetMode {
name: name.value.to_string(),
latch: true,
})
}
fn parse_create_virtual_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name,) = ext.extract((str("name"),))?;
Ok(Action::CreateVirtualOutput {
name: name.value.to_string(),
})
}
fn parse_remove_virtual_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (name,) = ext.extract((str("name"),))?;
Ok(Action::RemoveVirtualOutput {
name: name.value.to_string(),
})
}
fn parse_resize(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let (dx1, dy1, dx2, dy2) = ext.extract((
opt(s32("dx1")),
opt(s32("dy1")),
opt(s32("dx2")),
opt(s32("dy2")),
))?;
Ok(Action::Resize {
dx1: dx1.despan().unwrap_or(0),
dy1: dy1.despan().unwrap_or(0),
dx2: dx2.despan().unwrap_or(0),
dy2: dy2.despan().unwrap_or(0),
})
}
}
impl Parser for ActionParser<'_> {
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),
"move-to-output" => self.parse_move_to_output(&mut ext),
"set-repeat-rate" => self.parse_set_repeat_rate(&mut ext),
"define-action" => self.parse_define_action(&mut ext),
"undefine-action" => self.parse_undefine_action(&mut ext),
"named" => self.parse_named_action(&mut ext),
"create-mark" => self.parse_create_mark(&mut ext),
"jump-to-mark" => self.parse_jump_to_mark(&mut ext),
"copy-mark" => self.parse_copy_mark(&mut ext),
"push-mode" => self.parse_push_mode(&mut ext),
"latch-mode" => self.parse_latch_mode(&mut ext),
"create-virtual-output" => self.parse_create_virtual_output(&mut ext),
"remove-virtual-output" => self.parse_remove_virtual_output(&mut ext),
"resize" => self.parse_resize(&mut ext),
v => {
ext.ignore_unused();
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
}
};
drop(ext);
res
}
}

View file

@ -0,0 +1,78 @@
use {
crate::{
config::{
Action, NamedAction,
context::Context,
extractor::ExtractorError,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::action::ActionParser,
},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
std::{collections::HashSet, rc::Rc},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ActionsParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
ExtractorError(#[from] ExtractorError),
}
pub struct ActionsParser<'a, 'b> {
pub cx: &'a Context<'a>,
pub used_names: HashSet<Spanned<Rc<String>>>,
pub actions: &'b mut Vec<NamedAction>,
}
impl Parser for ActionsParser<'_, '_> {
type Value = ();
type Error = ActionsParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
for (name, value) in table.iter() {
let Some(action) = parse_action(self.cx, &name.value, value) else {
continue;
};
let name = Rc::new(name.value.clone()).spanned(name.span);
log_used(self.cx, &mut self.used_names, name.clone());
self.actions.push(NamedAction {
name: name.value,
action,
});
}
Ok(())
}
}
fn parse_action(cx: &Context<'_>, name: &str, value: &Spanned<Value>) -> Option<Action> {
match value.parse(&mut ActionParser(cx)) {
Ok(a) => Some(a),
Err(e) => {
log::warn!("Could not parse action for name {name}: {}", cx.error(e));
None
}
}
}
fn log_used(cx: &Context<'_>, used: &mut HashSet<Spanned<Rc<String>>>, key: Spanned<Rc<String>>) {
if let Some(prev) = used.get(&key) {
log::warn!(
"Duplicate actions overrides previous definition: {}",
cx.error3(key.span)
);
log::info!("Previous definition here: {}", cx.error3(prev.span));
}
used.insert(key);
}

View file

@ -0,0 +1,99 @@
use {
crate::{
config::{
AnimationCurveConfig, Animations,
context::Context,
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum AnimationsParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("Expected animation curve to be a string or an array")]
CurveType,
#[error("Cubic-bezier animation curves must contain exactly four values")]
CubicBezierLen,
#[error("Cubic-bezier animation curve entries must be finite floats or integers")]
CubicBezierValue,
#[error("Cubic-bezier x control points must be between 0 and 1")]
CubicBezierXRange,
}
pub struct AnimationsParser<'a>(pub &'a Context<'a>);
impl Parser for AnimationsParser<'_> {
type Value = Animations;
type Error = AnimationsParserError;
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 (enabled, duration_ms, style, curve) = ext.extract((
recover(opt(bol("enabled"))),
recover(opt(n32("duration-ms"))),
recover(opt(str("style"))),
opt(val("curve")),
))?;
let curve = match curve {
Some(curve) => Some(parse_curve(curve)?),
None => None,
};
Ok(Animations {
enabled: enabled.despan(),
duration_ms: duration_ms.despan(),
style: style.despan().map(|style| style.to_string()),
curve,
})
}
}
fn parse_curve(
curve: Spanned<&Value>,
) -> Result<AnimationCurveConfig, Spanned<AnimationsParserError>> {
match curve.value {
Value::String(s) => Ok(AnimationCurveConfig::Preset(s.clone())),
Value::Array(values) => parse_cubic_bezier(curve.span, values),
_ => Err(AnimationsParserError::CurveType.spanned(curve.span)),
}
}
fn parse_cubic_bezier(
span: Span,
values: &[Spanned<Value>],
) -> Result<AnimationCurveConfig, Spanned<AnimationsParserError>> {
if values.len() != 4 {
return Err(AnimationsParserError::CubicBezierLen.spanned(span));
}
let mut points = [0.0; 4];
for (idx, value) in values.iter().enumerate() {
let f = match value.value {
Value::Float(f) => f,
Value::Integer(i) => i as f64,
_ => return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)),
};
if !f.is_finite() {
return Err(AnimationsParserError::CubicBezierValue.spanned(value.span));
}
points[idx] = f as f32;
}
if !(0.0..=1.0).contains(&points[0]) || !(0.0..=1.0).contains(&points[2]) {
return Err(AnimationsParserError::CubicBezierXRange.spanned(span));
}
Ok(AnimationCurveConfig::CubicBezier(points))
}

View file

@ -0,0 +1,57 @@
use {
crate::{
config::{
context::Context,
extractor::{Extractor, ExtractorError, fltorint, opt},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
std::time::{Duration, TryFromFloatSecsError},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum CleanLogsOlderThanParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("At least one of the `weeks` or `days` fields must be specified")]
WeeksOrDays,
#[error("Duration is invalid")]
InvalidDuration(#[source] TryFromFloatSecsError),
}
pub struct CleanLogsOlderThanParser<'a>(pub &'a Context<'a>);
impl Parser for CleanLogsOlderThanParser<'_> {
type Value = Duration;
type Error = CleanLogsOlderThanParserError;
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 (weeks, days) = ext.extract((opt(fltorint("weeks")), opt(fltorint("days"))))?;
if weeks.is_none() && days.is_none() {
return Err(CleanLogsOlderThanParserError::WeeksOrDays.spanned(span));
}
const SECONDS_IN_WEEK: f64 = 604800.0;
const SECONDS_IN_DAY: f64 = 86400.0;
let duration = Duration::try_from_secs_f64(
weeks.despan().unwrap_or_default() * SECONDS_IN_WEEK
+ days.despan().unwrap_or_default() * SECONDS_IN_DAY,
)
.map_err(CleanLogsOlderThanParserError::InvalidDuration)
.map_err(|e| e.spanned(span))?;
Ok(duration)
}
}

View file

@ -0,0 +1,160 @@
use {
crate::{
config::{
ClientMatch, GenericMatch, MatchExactly,
context::Context,
extractor::{Extractor, ExtractorError, arr, bol, n32, opt, s32, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ClientMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct ClientMatchParser<'a>(pub &'a Context<'a>);
impl Parser for ClientMatchParser<'_> {
type Value = ClientMatch;
type Error = ClientMatchParserError;
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 (
(
name,
not_val,
all_val,
any_val,
exactly_val,
sandboxed,
sandbox_engine,
sandbox_engine_regex,
sandbox_app_id,
sandbox_app_id_regex,
),
(
sandbox_instance_id,
sandbox_instance_id_regex,
uid,
pid,
comm,
comm_regex,
exe,
exe_regex,
),
(is_xwayland,),
) = ext.extract((
(
opt(str("name")),
opt(val("not")),
opt(arr("all")),
opt(arr("any")),
opt(val("exactly")),
opt(bol("sandboxed")),
opt(str("sandbox-engine")),
opt(str("sandbox-engine-regex")),
opt(str("sandbox-app-id")),
opt(str("sandbox-app-id-regex")),
),
(
opt(str("sandbox-instance-id")),
opt(str("sandbox-instance-id-regex")),
opt(s32("uid")),
opt(s32("pid")),
opt(str("comm")),
opt(str("comm-regex")),
opt(str("exe")),
opt(str("exe-regex")),
),
(opt(bol("is-xwayland")),),
))?;
let mut not = None;
if let Some(value) = not_val {
not = Some(Box::new(value.parse(&mut ClientMatchParser(self.0))?));
}
macro_rules! list {
($val:expr) => {{
let mut list = None;
if let Some(value) = $val {
let mut res = vec![];
for value in value.value {
res.push(value.parse(&mut ClientMatchParser(self.0))?);
}
list = Some(res);
}
list
}};
}
let all = list!(all_val);
let any = list!(any_val);
let mut exactly = None;
if let Some(value) = exactly_val {
exactly = Some(value.parse(&mut ClientMatchExactlyParser(self.0))?);
}
Ok(ClientMatch {
generic: GenericMatch {
name: name.despan_into(),
not,
all,
any,
exactly,
},
sandbox_engine: sandbox_engine.despan_into(),
sandbox_engine_regex: sandbox_engine_regex.despan_into(),
sandbox_app_id: sandbox_app_id.despan_into(),
sandbox_app_id_regex: sandbox_app_id_regex.despan_into(),
sandbox_instance_id: sandbox_instance_id.despan_into(),
sandbox_instance_id_regex: sandbox_instance_id_regex.despan_into(),
sandboxed: sandboxed.despan(),
uid: uid.despan(),
pid: pid.despan(),
is_xwayland: is_xwayland.despan(),
comm: comm.despan_into(),
comm_regex: comm_regex.despan_into(),
exe: exe.despan_into(),
exe_regex: exe_regex.despan_into(),
})
}
}
pub struct ClientMatchExactlyParser<'a>(pub &'a Context<'a>);
impl Parser for ClientMatchExactlyParser<'_> {
type Value = MatchExactly<ClientMatch>;
type Error = ClientMatchParserError;
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 (num, list_val) = ext.extract((n32("num"), arr("list")))?;
let mut list = vec![];
for el in list_val.value {
list.push(el.parse(&mut ClientMatchParser(self.0))?);
}
Ok(MatchExactly {
num: num.value as _,
list,
})
}
}

View file

@ -0,0 +1,104 @@
use {
crate::{
config::{
ClientMatch, ClientRule,
context::Context,
extractor::{Extractor, ExtractorError, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::{ActionParser, ActionParserError},
client_match::{ClientMatchParser, ClientMatchParserError},
},
spanned::SpannedErrorExt,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ClientRuleParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] ClientMatchParserError),
#[error(transparent)]
Action(ActionParserError),
#[error(transparent)]
Latch(ActionParserError),
}
pub struct ClientRuleParser<'a>(pub &'a Context<'a>);
impl Parser for ClientRuleParser<'_> {
type Value = ClientRule;
type Error = ClientRuleParserError;
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 (name, match_val, action_val, latch_val) = ext.extract((
opt(str("name")),
opt(val("match")),
opt(val("action")),
opt(val("latch")),
))?;
let mut action = None;
if let Some(value) = action_val {
action = Some(
value
.parse(&mut ActionParser(self.0))
.map_spanned_err(ClientRuleParserError::Action)?,
);
}
let mut latch = None;
if let Some(value) = latch_val {
latch = Some(
value
.parse(&mut ActionParser(self.0))
.map_spanned_err(ClientRuleParserError::Latch)?,
);
}
let match_ = match match_val {
None => ClientMatch::default(),
Some(m) => m.parse_map(&mut ClientMatchParser(self.0))?,
};
Ok(ClientRule {
name: name.despan_into(),
match_,
action,
latch,
})
}
}
pub struct ClientRulesParser<'a>(pub &'a Context<'a>);
impl Parser for ClientRulesParser<'_> {
type Value = Vec<ClientRule>;
type Error = ClientRuleParserError;
const EXPECTED: &'static [DataType] = &[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 ClientRuleParser(self.0)) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse client rule: {}", self.0.error(e));
}
}
}
Ok(res)
}
}

View file

@ -0,0 +1,57 @@
use {
crate::{
config::{
extractor::ExtractorError,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::toml_span::{Span, SpannedExt},
},
jay_config::theme::Color,
std::{num::ParseIntError, ops::Range},
thiserror::Error,
};
pub struct ColorParser;
#[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(6..8)?),
_ => return Err(ColorParserError::Length.spanned(span)),
};
Ok(Color::new_straight(r, g, b, a))
}
}

View file

@ -0,0 +1,44 @@
use {
crate::{
config::{
ColorManagement,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ColorManagementParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct ColorManagementParser<'a>(pub &'a Context<'a>);
impl Parser for ColorManagementParser<'_> {
type Value = ColorManagement;
type Error = ColorManagementParserError;
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 (enabled,) = ext.extract((opt(bol("enabled")),))?;
Ok(ColorManagement {
enabled: enabled.despan(),
})
}
}

View file

@ -0,0 +1,629 @@
use {
crate::{
config::{
Action, Animations, Config, Libei, Theme, UiDrag,
context::Context,
extractor::{Extractor, ExtractorError, arr, bol, int, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::ActionParser,
actions::ActionsParser,
animations::AnimationsParser,
clean_logs_older_than::CleanLogsOlderThanParser,
client_rule::ClientRulesParser,
color_management::ColorManagementParser,
connector::ConnectorsParser,
drm_device::DrmDevicesParser,
drm_device_match::DrmDeviceMatchParser,
env::EnvParser,
fallback_output_mode::FallbackOutputModeParser,
float::FloatParser,
focus_history::FocusHistoryParser,
gfx_api::GfxApiParser,
idle::IdleParser,
input::InputsParser,
input_mode::InputModesParser,
keymap::KeymapParser,
libei::LibeiParser,
log_level::LogLevelParser,
output::OutputsParser,
repeat_rate::RepeatRateParser,
shortcuts::{
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
parse_modified_keysym_str,
},
simple_im::SimpleImParser,
status::StatusParser,
tearing::TearingParser,
theme::ThemeParser,
ui_drag::UiDragParser,
vrr::VrrParser,
window_rule::WindowRulesParser,
workspace_display_order::WorkspaceDisplayOrderParser,
xwayland::XwaylandParser,
},
spanned::SpannedErrorExt,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
ahash::AHashMap,
indexmap::IndexMap,
jay_config::keyboard::syms::KeySym,
kbvm::Keysym,
std::collections::HashSet,
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,
),
(
explicit_sync,
repeat_rate_val,
complex_shortcuts_val,
focus_follows_mouse,
window_management_key_val,
vrr_val,
tearing_val,
libei_val,
ui_drag_val,
xwayland_val,
),
(
color_management_val,
float_val,
actions_val,
max_action_depth_val,
client_rules_val,
window_rules_val,
pointer_revert_key_str,
use_hardware_cursor,
show_bar,
focus_history_val,
),
(
middle_click_paste,
input_modes_val,
workspace_display_order_val,
auto_reload,
simple_im_val,
show_titles,
fallback_output_mode_val,
clean_logs_older_than_val,
mouse_follows_focus,
animations_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")),
),
(
recover(opt(bol("explicit-sync"))),
opt(val("repeat-rate")),
opt(val("complex-shortcuts")),
recover(opt(bol("focus-follows-mouse"))),
recover(opt(str("window-management-key"))),
opt(val("vrr")),
opt(val("tearing")),
opt(val("libei")),
opt(val("ui-drag")),
opt(val("xwayland")),
),
(
opt(val("color-management")),
opt(val("float")),
opt(val("actions")),
recover(opt(int("max-action-depth"))),
opt(val("clients")),
opt(val("windows")),
recover(opt(str("pointer-revert-key"))),
recover(opt(bol("use-hardware-cursor"))),
recover(opt(bol("show-bar"))),
opt(val("focus-history")),
),
(
recover(opt(bol("middle-click-paste"))),
opt(val("modes")),
opt(val("workspace-display-order")),
recover(opt(bol("auto-reload"))),
opt(val("simple-im")),
recover(opt(bol("show-titles"))),
opt(val("fallback-output-mode")),
opt(val("clean-logs-older-than")),
recover(opt(bol("unstable-mouse-follows-focus"))),
opt(val("animations")),
),
))?;
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 used_keys = HashSet::new();
let mut shortcuts = vec![];
if let Some(value) = shortcuts_val {
value
.parse(&mut ShortcutsParser {
cx: self.0,
used_keys: &mut used_keys,
shortcuts: &mut shortcuts,
})
.map_spanned_err(ConfigParserError::ParseShortcuts)?;
}
if let Some(value) = complex_shortcuts_val {
value
.parse(&mut ComplexShortcutsParser {
cx: self.0,
used_keys: &mut used_keys,
shortcuts: &mut shortcuts,
})
.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 clean_logs_older_than = None;
if let Some(value) = clean_logs_older_than_val {
match value.parse(&mut CleanLogsOlderThanParser(self.0)) {
Ok(v) => {
clean_logs_older_than = Some(v);
}
Err(e) => {
log::warn!("Could not parse clean-logs-older-than: {}", 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;
let mut grace_period = None;
let mut key_press_enables_dpms = None;
let mut mouse_move_enables_dpms = None;
if let Some(value) = idle_val {
match value.parse(&mut IdleParser(self.0)) {
Ok(v) => {
idle = v.timeout;
grace_period = v.grace_period;
key_press_enables_dpms = v.key_press_enables_dpms;
mouse_move_enables_dpms = v.mouse_move_enables_dpms;
}
Err(e) => {
log::warn!("Could not parse the idle timeout: {}", self.0.error(e));
}
}
}
let mut repeat_rate = None;
if let Some(value) = repeat_rate_val {
match value.parse(&mut RepeatRateParser(self.0)) {
Ok(v) => repeat_rate = Some(v),
Err(e) => {
log::warn!("Could not parse the repeat rate: {}", self.0.error(e));
}
}
}
let mut window_management_key = None;
if let Some(value) = window_management_key_val
&& let Some(key) = parse_modified_keysym_str(self.0, value.span, value.value)
{
window_management_key = Some(key);
}
let mut vrr = None;
if let Some(value) = vrr_val {
match value.parse(&mut VrrParser(self.0)) {
Ok(v) => vrr = Some(v),
Err(e) => {
log::warn!("Could not parse VRR setting: {}", self.0.error(e));
}
}
}
let mut tearing = None;
if let Some(value) = tearing_val {
match value.parse(&mut TearingParser(self.0)) {
Ok(v) => tearing = Some(v),
Err(e) => {
log::warn!("Could not parse tearing setting: {}", self.0.error(e));
}
}
}
let mut libei = Libei::default();
if let Some(value) = libei_val {
match value.parse(&mut LibeiParser(self.0)) {
Ok(v) => libei = v,
Err(e) => {
log::warn!("Could not parse libei setting: {}", self.0.error(e));
}
}
}
let mut ui_drag = UiDrag::default();
if let Some(value) = ui_drag_val {
match value.parse(&mut UiDragParser(self.0)) {
Ok(v) => ui_drag = v,
Err(e) => {
log::warn!("Could not parse ui-drag setting: {}", self.0.error(e));
}
}
}
let mut animations = Animations::default();
if let Some(value) = animations_val {
match value.parse(&mut AnimationsParser(self.0)) {
Ok(v) => animations = v,
Err(e) => {
log::warn!("Could not parse animations setting: {}", self.0.error(e));
}
}
}
let mut xwayland = None;
if let Some(value) = xwayland_val {
match value.parse(&mut XwaylandParser(self.0)) {
Ok(v) => xwayland = Some(v),
Err(e) => {
log::warn!("Could not parse Xwayland setting: {}", self.0.error(e));
}
}
}
let mut color_management = None;
if let Some(value) = color_management_val {
match value.parse(&mut ColorManagementParser(self.0)) {
Ok(v) => color_management = Some(v),
Err(e) => {
log::warn!(
"Could not parse the color-management settings: {}",
self.0.error(e)
);
}
}
}
let mut float = None;
if let Some(value) = float_val {
match value.parse(&mut FloatParser(self.0)) {
Ok(v) => float = Some(v),
Err(e) => {
log::warn!("Could not parse the float settings: {}", self.0.error(e));
}
}
}
let mut named_actions = vec![];
if let Some(value) = actions_val {
let mut parser = ActionsParser {
cx: self.0,
used_names: Default::default(),
actions: &mut named_actions,
};
if let Err(e) = value.parse(&mut parser) {
log::warn!("Could not parse named actions: {}", self.0.error(e));
}
}
let mut max_action_depth = 16;
if let Some(mut value) = max_action_depth_val {
if value.value < 0 {
log::warn!(
"Max action depth should not be negative: {}",
self.0.error3(value.span)
);
value.value = 0;
}
max_action_depth = value.value as _;
}
let mut client_rules = vec![];
if let Some(value) = client_rules_val {
match value.parse(&mut ClientRulesParser(self.0)) {
Ok(v) => client_rules = v,
Err(e) => log::warn!("Could not parse the client rules: {}", self.0.error(e)),
}
}
let mut window_rules = vec![];
if let Some(value) = window_rules_val {
match value.parse(&mut WindowRulesParser(self.0)) {
Ok(v) => window_rules = v,
Err(e) => log::warn!("Could not parse the window rules: {}", self.0.error(e)),
}
}
let mut pointer_revert_key = None;
if let Some(value) = pointer_revert_key_str {
match Keysym::from_str(value.value) {
Some(s) => pointer_revert_key = Some(KeySym(s.0)),
None => log::warn!("Unknown keysym: {}", self.0.error3(value.span)),
}
}
let mut focus_history = None;
if let Some(value) = focus_history_val {
match value.parse(&mut FocusHistoryParser(self.0)) {
Ok(v) => focus_history = Some(v),
Err(e) => {
log::warn!(
"Could not parse the focus-history settings: {}",
self.0.error(e)
);
}
}
}
let mut input_modes = AHashMap::new();
if let Some(value) = input_modes_val {
match value.parse(&mut InputModesParser(self.0)) {
Ok(v) => input_modes = v,
Err(e) => {
log::warn!("Could not parse the input modes: {}", self.0.error(e),);
}
}
}
let mut workspace_display_order = None;
if let Some(value) = workspace_display_order_val {
match value.parse(&mut WorkspaceDisplayOrderParser) {
Ok(v) => workspace_display_order = Some(v),
Err(e) => {
log::warn!(
"Could not parse the workspace display order: {}",
self.0.error(e)
);
}
}
}
let mut simple_im = None;
if let Some(value) = simple_im_val {
match value.parse(&mut SimpleImParser(self.0)) {
Ok(v) => simple_im = Some(v),
Err(e) => {
log::warn!("Could not parse simple IM setting: {}", self.0.error(e));
}
}
}
let mut fallback_output_mode = None;
if let Some(value) = fallback_output_mode_val {
match value.parse(&mut FallbackOutputModeParser) {
Ok(v) => fallback_output_mode = Some(v),
Err(e) => {
log::warn!(
"Could not parse the fallback output mode: {}",
self.0.error(e)
);
}
}
}
Ok(Config {
keymap,
repeat_rate,
shortcuts,
on_graphics_initialized,
on_idle,
status,
outputs,
connectors,
workspace_capture: workspace_capture.despan().unwrap_or(true),
env,
on_startup,
keymaps,
auto_reload: auto_reload.despan(),
log_level,
clean_logs_older_than,
theme,
gfx_api,
drm_devices,
direct_scanout_enabled: direct_scanout.despan(),
explicit_sync_enabled: explicit_sync.despan(),
render_device,
inputs,
idle,
grace_period,
key_press_enables_dpms,
mouse_move_enables_dpms,
focus_follows_mouse: focus_follows_mouse.despan().unwrap_or(true),
window_management_key,
vrr,
tearing,
libei,
ui_drag,
animations,
xwayland,
color_management,
float,
named_actions,
max_action_depth,
client_rules,
window_rules,
pointer_revert_key,
use_hardware_cursor: use_hardware_cursor.despan(),
show_bar: show_bar.despan(),
show_titles: show_titles.despan(),
focus_history,
middle_click_paste: middle_click_paste.despan(),
input_modes,
workspace_display_order,
simple_im,
fallback_output_mode,
mouse_follows_focus: mouse_follows_focus.despan(),
})
}
}

View file

@ -0,0 +1,83 @@
use {
crate::{
config::{
ConfigConnector,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::connector_match::{ConnectorMatchParser, ConnectorMatchParserError},
},
jay_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 Parser for ConnectorParser<'_> {
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 Parser for ConnectorsParser<'_> {
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::{
ConnectorMatch,
context::Context,
extractor::{Extractor, ExtractorError, opt, str},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_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 Parser for ConnectorMatchParser<'_> {
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,53 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
jay_config::window::{
ContentType, GAME_CONTENT, NO_CONTENT_TYPE, PHOTO_CONTENT, VIDEO_CONTENT,
},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ContentTypeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown content type `{}`", .0)]
UnknownContentType(String),
}
pub struct ContentTypeParser;
impl Parser for ContentTypeParser {
type Value = ContentType;
type Error = ContentTypeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let ty = match string {
"none" => NO_CONTENT_TYPE,
"any" => !NO_CONTENT_TYPE,
"photo" => PHOTO_CONTENT,
"video" => VIDEO_CONTENT,
"game" => GAME_CONTENT,
_ => {
return Err(
ContentTypeParserError::UnknownContentType(string.to_owned()).spanned(span),
);
}
};
Ok(ty)
}
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut ty = ContentType(0);
for el in array {
ty |= el.parse(&mut ContentTypeParser)?;
}
Ok(ty)
}
}

View file

@ -0,0 +1,129 @@
use {
crate::{
config::{
ConfigDrmDevice,
context::Context,
extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
gfx_api::GfxApiParser,
},
},
jay_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 Parser for DrmDeviceParser<'_> {
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, flip_margin_ms) =
ext.extract((
opt(str("name")),
val("match"),
recover(opt(bol("direct-scanout"))),
opt(val("gfx-api")),
recover(opt(fltorint("flip-margin-ms"))),
))?;
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,
flip_margin_ms: flip_margin_ms.despan(),
})
}
}
pub struct DrmDevicesParser<'a>(pub &'a Context<'a>);
impl Parser for DrmDevicesParser<'_> {
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::{
DrmDeviceMatch,
context::Context,
extractor::{Extractor, ExtractorError, n32, opt, recover, str},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_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},
},
jay_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,125 @@
use {
crate::{
config::{
Exec,
context::Context,
extractor::{Extractor, ExtractorError, arr, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
StringParser, StringParserError,
env::{EnvParser, EnvParserError},
},
},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
std::sync::LazyLock,
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,
#[error("Exactly one of the `prog` or `shell` fields must be specified")]
ProgXorShell,
#[error("Could not read $SHELL")]
ShellNotDefined,
#[error("The `args` field cannot be used for shell commands")]
ArgsForShell,
}
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_opt, shell_opt, args_val, envs_val) = ext.extract((
opt(str("prog")),
opt(str("shell")),
opt(arr("args")),
opt(val("env")),
))?;
let prog;
let mut args = vec![];
match (prog_opt, shell_opt) {
(None, None) | (Some(_), Some(_)) => {
return Err(ExecParserError::ProgXorShell.spanned(span));
}
(Some(v), _) => {
prog = v.value.to_string();
if let Some(args_val) = args_val {
for arg in args_val.value {
args.push(arg.parse_map(&mut StringParser)?);
}
}
}
(_, Some(v)) => {
prog = shell(v.span)?;
args = vec!["-c".to_string(), v.value.to_string()];
if let Some(v) = args_val {
return Err(ExecParserError::ArgsForShell.spanned(v.span));
}
}
}
let envs = match envs_val {
None => vec![],
Some(e) => e.parse_map(&mut EnvParser)?,
};
Ok(Exec {
prog,
args,
envs,
})
}
}
fn shell(span: Span) -> Result<String, Spanned<ExecParserError>> {
static SHELL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("SHELL").ok());
SHELL
.clone()
.ok_or(ExecParserError::ShellNotDefined.spanned(span))
}

View file

@ -0,0 +1,38 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::toml_span::{Span, SpannedExt},
},
jay_config::input::FallbackOutputMode,
thiserror::Error,
};
pub struct FallbackOutputModeParser;
#[derive(Debug, Error)]
pub enum FallbackOutputModeParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error("Unknown mode {0}")]
Unknown(String),
}
impl Parser for FallbackOutputModeParser {
type Value = FallbackOutputMode;
type Error = FallbackOutputModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
use FallbackOutputMode::*;
let api = match string.to_ascii_lowercase().as_str() {
"cursor" => Cursor,
"focus" => Focus,
_ => {
return Err(
FallbackOutputModeParserError::Unknown(string.to_string()).spanned(span)
);
}
};
Ok(api)
}
}

View file

@ -0,0 +1,44 @@
use {
crate::{
config::{
Float,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, recover},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum FloatParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct FloatParser<'a>(pub &'a Context<'a>);
impl Parser for FloatParser<'_> {
type Value = Float;
type Error = FloatParserError;
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 (show_pin_icon,) = ext.extract((recover(opt(bol("show-pin-icon"))),))?;
Ok(Float {
show_pin_icon: show_pin_icon.despan(),
})
}
}

View file

@ -0,0 +1,48 @@
use {
crate::{
config::{
FocusHistory,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, recover},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum FocusHistoryParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct FocusHistoryParser<'a>(pub &'a Context<'a>);
impl Parser for FocusHistoryParser<'_> {
type Value = FocusHistory;
type Error = FocusHistoryParserError;
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 (only_visible, same_workspace) = ext.extract((
recover(opt(bol("only-visible"))),
recover(opt(bol("same-workspace"))),
))?;
Ok(FocusHistory {
only_visible: only_visible.despan(),
same_workspace: same_workspace.despan(),
})
}
}

View file

@ -0,0 +1,67 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::toml_span::{Span, SpannedExt},
},
jay_config::video::Format,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum FormatParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown format {0}")]
UnknownFormat(String),
}
pub struct FormatParser;
impl Parser for FormatParser {
type Value = Format;
type Error = FormatParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let format = match string {
"argb8888" => Format::ARGB8888,
"xrgb8888" => Format::XRGB8888,
"abgr8888" => Format::ABGR8888,
"xbgr8888" => Format::XBGR8888,
"r8" => Format::R8,
"gr88" => Format::GR88,
"rgb888" => Format::RGB888,
"bgr888" => Format::BGR888,
"rgba4444" => Format::RGBA4444,
"rgbx4444" => Format::RGBX4444,
"bgra4444" => Format::BGRA4444,
"bgrx4444" => Format::BGRX4444,
"rgb565" => Format::RGB565,
"bgr565" => Format::BGR565,
"rgba5551" => Format::RGBA5551,
"rgbx5551" => Format::RGBX5551,
"bgra5551" => Format::BGRA5551,
"bgrx5551" => Format::BGRX5551,
"argb1555" => Format::ARGB1555,
"xrgb1555" => Format::XRGB1555,
"argb2101010" => Format::ARGB2101010,
"xrgb2101010" => Format::XRGB2101010,
"abgr2101010" => Format::ABGR2101010,
"xbgr2101010" => Format::XBGR2101010,
"abgr16161616" => Format::ABGR16161616,
"xbgr16161616" => Format::XBGR16161616,
"abgr16161616f" => Format::ABGR16161616F,
"xbgr16161616f" => Format::XBGR16161616F,
"bgr161616" => Format::BGR161616,
"r16f" => Format::R16F,
"gr1616f" => Format::GR1616F,
"bgr161616f" => Format::BGR161616F,
"r32f" => Format::R32F,
"gr3232f" => Format::GR3232F,
"bgr323232f" => Format::BGR323232F,
"abgr32323232f" => Format::ABGR32323232F,
_ => return Err(FormatParserError::UnknownFormat(string.to_string()).spanned(span)),
};
Ok(format)
}
}

View file

@ -0,0 +1,34 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_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,99 @@
use {
crate::{
config::{
context::Context,
extractor::{Extractor, ExtractorError, bol, n64, opt, recover, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_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>);
pub struct Idle {
pub timeout: Option<Duration>,
pub grace_period: Option<Duration>,
pub key_press_enables_dpms: Option<bool>,
pub mouse_move_enables_dpms: Option<bool>,
}
impl Parser for IdleParser<'_> {
type Value = Idle;
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,
grace_period_val,
key_press_enables_dpms,
mouse_move_enables_dpms,
) = ext.extract((
opt(n64("minutes")),
opt(n64("seconds")),
opt(val("grace-period")),
recover(opt(bol("key-press-enables-dpms"))),
recover(opt(bol("mouse-move-enables-dpms"))),
))?;
let mut timeout = None;
if minutes.is_some() || seconds.is_some() {
timeout = Some(parse_duration(&minutes, &seconds));
}
let mut grace_period = None;
if let Some(gp) = grace_period_val {
grace_period = Some(gp.parse(&mut GracePeriodParser(self.0))?);
}
Ok(Idle {
timeout,
grace_period,
key_press_enables_dpms: key_press_enables_dpms.despan(),
mouse_move_enables_dpms: mouse_move_enables_dpms.despan(),
})
}
}
struct GracePeriodParser<'a>(pub &'a Context<'a>);
impl Parser for GracePeriodParser<'_> {
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 grace_period = parse_duration(&minutes, &seconds);
Ok(grace_period)
}
}
fn parse_duration(minutes: &Option<Spanned<u64>>, seconds: &Option<Spanned<u64>>) -> Duration {
Duration::from_secs(
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
)
}

View file

@ -0,0 +1,393 @@
use {
crate::{
config::{
Input,
context::Context,
extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::ActionParser,
input_match::{InputMatchParser, InputMatchParserError},
keymap::KeymapParser,
output_match::OutputMatchParser,
},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
ahash::AHashMap,
indexmap::IndexMap,
jay_config::input::{
SwitchEvent,
acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT},
clickmethod::{CLICK_METHOD_BUTTON_AREAS, CLICK_METHOD_CLICKFINGER, CLICK_METHOD_NONE},
},
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,
#[error("Calibration matrix must have exactly two rows")]
CaliTwoRows,
#[error("Calibration matrix must have exactly three columns")]
CaliThreeColumns,
#[error("Calibration matrix entries must be floats")]
CaliFloat,
}
pub struct InputParser<'a> {
pub cx: &'a Context<'a>,
pub is_inputs_array: bool,
}
impl Parser for InputParser<'_> {
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,
keymap,
on_lid_opened_val,
on_lid_closed_val,
on_converted_to_laptop_val,
on_converted_to_tablet_val,
output_val,
remove_mapping,
calibration_matrix,
click_method,
),
(middle_button_emulation,),
) = 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"))),
opt(val("keymap")),
opt(val("on-lid-opened")),
opt(val("on-lid-closed")),
opt(val("on-converted-to-laptop")),
opt(val("on-converted-to-tablet")),
opt(val("output")),
recover(opt(bol("remove-mapping"))),
recover(opt(val("calibration-matrix"))),
recover(opt(str("click-method"))),
),
(recover(opt(bol("middle-button-emulation"))),),
))?;
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 click_method = match click_method {
None => None,
Some(p) => match p.value.to_ascii_lowercase().as_str() {
"none" => Some(CLICK_METHOD_NONE),
"button-areas" => Some(CLICK_METHOD_BUTTON_AREAS),
"clickfinger" => Some(CLICK_METHOD_CLICKFINGER),
v => {
log::warn!("Unknown click-method {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.is_inputs_array {
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)
);
}
}
let keymap = match keymap {
None => None,
Some(map) => match map.parse(&mut KeymapParser {
cx: self.cx,
definition: false,
}) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse keymap: {}", self.cx.error(e));
None
}
},
};
let mut switch_actions = AHashMap::new();
let mut parse_action = |val: Option<Spanned<&Value>>, name, event| {
if let Some(val) = val {
if !self.is_inputs_array {
log::warn!(
"{name} has no effect in this position: {}",
self.cx.error3(val.span)
);
return;
}
match val.parse(&mut ActionParser(self.cx)) {
Ok(a) => {
switch_actions.insert(event, a);
}
Err(e) => {
log::warn!("Could not parse {name} action: {}", self.cx.error(e));
}
}
}
};
parse_action(on_lid_opened_val, "on-lid-opened", SwitchEvent::LidOpened);
parse_action(on_lid_closed_val, "on-lid-closed", SwitchEvent::LidClosed);
parse_action(
on_converted_to_laptop_val,
"on-converted-to-laptop",
SwitchEvent::ConvertedToLaptop,
);
parse_action(
on_converted_to_tablet_val,
"on-converted-to-tablet",
SwitchEvent::ConvertedToTablet,
);
let mut output = None;
if let Some(val) = output_val {
match val.parse(&mut OutputMatchParser(self.cx)) {
Ok(v) => output = Some(Some(v)),
Err(e) => {
log::warn!("Could not parse output: {}", self.cx.error(e));
}
}
}
if let Some(val) = remove_mapping {
if self.is_inputs_array {
log::warn!(
"`remove-mapping` has no effect in this position: {}",
self.cx.error3(val.span)
);
} else if !val.value {
log::warn!(
"`remove-mapping = false` has no effect: {}",
self.cx.error3(val.span)
);
} else if let Some(output) = output_val {
log::warn!(
"Ignoring `remove-mapping = true` due to conflicting `output` field: {}",
self.cx.error3(val.span)
);
log::info!(
"`output` field defined here: {}",
self.cx.error3(output.span)
);
} else {
output = Some(None);
}
}
let calibration_matrix = match calibration_matrix {
None => None,
Some(matrix) => match matrix.parse(&mut CalibrationMatrixParser) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse calibration matrix: {}", self.cx.error(e));
None
}
},
};
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(),
middle_button_emulation: middle_button_emulation.despan(),
click_method,
px_per_wheel_scroll: px_per_wheel_scroll.despan(),
transform_matrix,
keymap,
switch_actions,
output,
calibration_matrix,
})
}
}
pub struct InputsParser<'a>(pub &'a Context<'a>);
impl Parser for InputsParser<'_> {
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,
is_inputs_array: 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,
is_inputs_array: 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])?])
}
}
struct CalibrationMatrixParser;
impl Parser for CalibrationMatrixParser {
type Value = [[f32; 3]; 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::CaliTwoRows.spanned(span));
}
Ok([
array[0].parse(&mut CalibrationMatrixRowParser)?,
array[1].parse(&mut CalibrationMatrixRowParser)?,
])
}
}
struct CalibrationMatrixRowParser;
impl Parser for CalibrationMatrixRowParser {
type Value = [f32; 3];
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array];
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.len() != 3 {
return Err(InputParserError::CaliThreeColumns.spanned(span));
}
let extract = |v: &Spanned<Value>| match v.value {
Value::Float(f) => Ok(f as f32),
Value::Integer(f) => Ok(f as _),
_ => Err(InputParserError::CaliFloat.spanned(v.span)),
};
Ok([
extract(&array[0])?,
extract(&array[1])?,
extract(&array[2])?,
])
}
}

View file

@ -0,0 +1,98 @@
use {
crate::{
config::{
InputMatch,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, str},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_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 Parser for InputMatchParser<'_> {
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,119 @@
use {
crate::{
config::{
InputMode,
context::Context,
extractor::{Extractor, ExtractorError, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::shortcuts::{ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError},
spanned::SpannedErrorExt,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
ahash::{AHashMap, AHashSet},
indexmap::IndexMap,
std::collections::HashSet,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum InputModeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
ExtractorError(#[from] ExtractorError),
#[error("Could not parse the shortcuts")]
ParseShortcuts(#[source] ShortcutsParserError),
}
pub struct InputModesParser<'a>(pub &'a Context<'a>);
impl Parser for InputModesParser<'_> {
type Value = AHashMap<String, InputMode>;
type Error = InputModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut modes = AHashMap::new();
let mut used = AHashSet::new();
for (key, value) in table.iter() {
let mode = match value.parse(&mut InputModeParser(self.0)) {
Ok(m) => m,
Err(e) => {
log::warn!(
"Could not parse input mode {}: {}",
key.value,
self.0.error(e)
);
continue;
}
};
log_used(self.0, &mut used, key);
modes.insert(key.value.to_string(), mode);
}
Ok(modes)
}
}
pub struct InputModeParser<'a>(pub &'a Context<'a>);
impl Parser for InputModeParser<'_> {
type Value = InputMode;
type Error = InputModeParserError;
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 (parent, shortcuts_val, complex_shortcuts_val) = ext.extract((
recover(opt(str("parent"))),
opt(val("shortcuts")),
opt(val("complex-shortcuts")),
))?;
let mut used_keys = HashSet::new();
let mut shortcuts = vec![];
if let Some(value) = shortcuts_val {
value
.parse(&mut ShortcutsParser {
cx: self.0,
used_keys: &mut used_keys,
shortcuts: &mut shortcuts,
})
.map_spanned_err(InputModeParserError::ParseShortcuts)?;
}
if let Some(value) = complex_shortcuts_val {
value
.parse(&mut ComplexShortcutsParser {
cx: self.0,
used_keys: &mut used_keys,
shortcuts: &mut shortcuts,
})
.map_spanned_err(InputModeParserError::ParseShortcuts)?;
}
Ok(InputMode {
parent: parent.despan_into(),
shortcuts,
})
}
}
fn log_used(cx: &Context<'_>, used: &mut AHashSet<Spanned<String>>, key: &Spanned<String>) {
if let Some(prev) = used.get(key) {
log::warn!(
"Duplicate input mode overrides previous definition: {}",
cx.error3(key.span)
);
log::info!("Previous definition here: {}", cx.error3(prev.span));
}
used.insert(key.clone());
}

View file

@ -0,0 +1,197 @@
use {
crate::{
config::{
ConfigKeymap,
context::Context,
extractor::{Extractor, ExtractorError, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::{
config_dir,
keyboard::{Keymap, keymap_from_names, parse_keymap},
},
kbvm::xkb::rmlvo::Group,
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`, `path`, `rmlvo`")]
MissingField,
#[error(
"Keymap must have both `name` and one of `map`, `path`, `rmlvo` 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, mut rmlvo) = ext.extract((
opt(str("name")),
opt(str("map")),
opt(str("path")),
opt(val("rmlvo")),
))?;
if map_val.is_some() as u32 + path.is_some() as u32 + rmlvo.is_some() as u32 > 1 {
log::warn!(
"At most one of `map`, `path`, and `rmlvo` should be specified: {}",
self.cx.error3(span),
);
let ignore_path = map_val.is_some();
let ignore_rmlvo = map_val.is_some() || path.is_some();
if ignore_path && path.is_some() {
log::warn!("Ignoring `path`");
path = None;
}
if ignore_rmlvo && rmlvo.is_some() {
log::warn!("Ignoring `rmlvo`");
rmlvo = 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));
}
let mut map = None;
if let Some(val) = &map_val {
map = Some(parse(val.span, val.value)?);
}
if let Some(val) = rmlvo {
map = Some(val.parse(&mut RmlvoParser(self.cx))?);
}
if self.definition && (name_val.is_none() || map.is_none()) {
return Err(KeymapParserError::DefinitionRequired.spanned(span));
}
if !self.definition && map.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) {
(Some(name_val), Some(map)) => ConfigKeymap::Defined {
name: name_val.value.to_string(),
map,
},
(Some(name_val), None) => ConfigKeymap::Named(name_val.value.to_string()),
(None, Some(map)) => ConfigKeymap::Literal(map),
(None, None) => return Err(KeymapParserError::MissingField.spanned(span)),
};
Ok(res)
}
}
struct RmlvoParser<'a>(&'a Context<'a>);
impl Parser for RmlvoParser<'_> {
type Value = Keymap;
type Error = KeymapParserError;
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 (rules, model, layout, variants, options) = ext.extract((
opt(str("rules")),
opt(str("model")),
opt(str("layout")),
opt(str("variants")),
opt(str("options")),
))?;
let mut groups = None::<Vec<_>>;
if layout.is_some() || variants.is_some() {
groups = Some(
Group::from_layouts_and_variants(
layout.despan().unwrap_or_default(),
variants.despan().unwrap_or_default(),
)
.map(|g| jay_config::keyboard::Group {
layout: g.layout,
variant: g.variant,
})
.collect(),
);
}
let mut options_vec = None::<Vec<_>>;
if let Some(options) = options {
options_vec = Some(options.value.split(",").collect());
}
let map = keymap_from_names(
rules.despan(),
model.despan(),
groups.as_deref(),
options_vec.as_deref(),
);
match map.is_valid() {
true => Ok(map),
false => Err(KeymapParserError::Invalid.spanned(span)),
}
}
}
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,44 @@
use {
crate::{
config::{
Libei,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, recover},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum LibeiParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct LibeiParser<'a>(pub &'a Context<'a>);
impl Parser for LibeiParser<'_> {
type Value = Libei;
type Error = LibeiParserError;
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 enable_socket = ext.extract(recover(opt(bol("enable-socket"))))?;
Ok(Libei {
enable_socket: enable_socket.despan(),
})
}
}

View file

@ -0,0 +1,37 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_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,61 @@
use {
crate::{
config::{
context::Context,
extractor::{Extractor, ExtractorError, opt, str},
keycodes::KEYCODES,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum MarkIdParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("MarkId must have exactly one field set")]
ExactlyOneField,
#[error("Unknown key {0}")]
UnknownKey(String),
}
pub struct MarkIdParser<'a>(pub &'a Context<'a>);
impl Parser for MarkIdParser<'_> {
type Value = u32;
type Error = MarkIdParserError;
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 (key, name) = ext.extract((opt(str("key")), opt(str("name"))))?;
let id = match (key, name) {
(None, None) | (Some(_), Some(_)) => {
return Err(MarkIdParserError::ExactlyOneField.spanned(span));
}
(Some(key), _) => match KEYCODES.get(key.value) {
Some(c) => *c,
_ => return Err(key.map(|s| MarkIdParserError::UnknownKey(s.to_string()))),
},
(_, Some(name)) => {
let mn = &mut *self.0.mark_names.borrow_mut();
let len = mn.len() as u32;
*mn.entry(name.value.to_string())
.or_insert(u32::MAX - 8 - len)
}
};
Ok(id)
}
}

View file

@ -0,0 +1,47 @@
use {
crate::{
config::{
Mode,
context::Context,
extractor::{Extractor, ExtractorError, fltorint, opt, s32},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_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 Parser for ModeParser<'_> {
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(fltorint("refresh-rate"))))?;
Ok(Mode {
width: width.value,
height: height.value,
refresh_rate: refresh_rate.despan(),
})
}
}

View file

@ -0,0 +1,107 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::toml_span::{Span, SpannedExt},
},
jay_config::keyboard::{
ModifiedKeySym,
mods::{
ALT, CAPS, CTRL, LOCK, LOGO, MOD1, MOD2, MOD3, MOD4, MOD5, Modifiers, NUM, RELEASE,
SHIFT,
},
syms::KeySym,
},
kbvm::Keysym,
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),
#[error("Unknown modifier {0}")]
UnknownModifier(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 parse_mod(part) {
Some(m) => m,
_ => match Keysym::from_str(part) {
Some(new) if sym.is_none() => {
sym = Some(KeySym(new.0));
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)),
}
}
}
pub struct ModifiersParser;
impl Parser for ModifiersParser {
type Value = Modifiers;
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);
if !string.is_empty() {
for part in string.split("-") {
let Some(modifier) = parse_mod(part) else {
return Err(
ModifiedKeysymParserError::UnknownModifier(part.to_string()).spanned(span)
);
};
modifiers |= modifier;
}
}
Ok(modifiers)
}
}
fn parse_mod(part: &str) -> Option<Modifiers> {
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,
"release" => RELEASE,
_ => return None,
};
Some(modifier)
}

View file

@ -0,0 +1,287 @@
use {
crate::{
config::{
Output,
context::Context,
extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, s32, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
format::FormatParser,
mode::ModeParser,
output_match::{OutputMatchParser, OutputMatchParserError},
tearing::TearingParser,
vrr::VrrParser,
},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::video::{BlendSpace, ColorSpace, Eotf, 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 Parser for OutputParser<'_> {
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, vrr_val, tearing_val, format_val),
(color_space, eotf, brightness_val, blend_space, use_native_gamut),
) = 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")),
opt(val("vrr")),
opt(val("tearing")),
opt(val("format")),
),
(
recover(opt(str("color-space"))),
recover(opt(str("transfer-function"))),
opt(val("brightness")),
recover(opt(str("blend-space"))),
recover(opt(bol("use-native-gamut"))),
),
))?;
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 color_space = match color_space {
None => None,
Some(cs) => match cs.value {
"default" => Some(ColorSpace::DEFAULT),
"bt2020" => Some(ColorSpace::BT2020),
_ => {
log::warn!(
"Unknown color space {}: {}",
cs.value,
self.cx.error3(cs.span)
);
None
}
},
};
let eotf = match eotf {
None => None,
Some(tf) => match tf.value {
"default" => Some(Eotf::DEFAULT),
"pq" => Some(Eotf::PQ),
_ => {
log::warn!("Unknown EOTF {}: {}", tf.value, self.cx.error3(tf.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)
);
}
}
let mut vrr = None;
if let Some(value) = vrr_val {
match value.parse(&mut VrrParser(self.cx)) {
Ok(v) => vrr = Some(v),
Err(e) => {
log::warn!("Could not parse VRR setting: {}", self.cx.error(e));
}
}
}
let mut tearing = None;
if let Some(value) = tearing_val {
match value.parse(&mut TearingParser(self.cx)) {
Ok(v) => tearing = Some(v),
Err(e) => {
log::warn!("Could not parse tearing setting: {}", self.cx.error(e));
}
}
}
let mut format = None;
if let Some(value) = format_val {
match value.parse(&mut FormatParser) {
Ok(v) => format = Some(v),
Err(e) => {
log::warn!(
"Could not parse framebuffer format setting: {}",
self.cx.error(e)
);
}
}
}
let mut brightness = None;
if let Some(value) = brightness_val {
match value.parse(&mut BrightnessParser) {
Ok(v) => brightness = Some(v),
Err(e) => {
log::warn!("Could not parse brightness setting: {}", self.cx.error(e));
}
}
}
let blend_space = match blend_space {
None => None,
Some(bs) => match bs.value {
"linear" => Some(BlendSpace::LINEAR),
"srgb" => Some(BlendSpace::SRGB),
_ => {
log::warn!(
"Unknown blend space {}: {}",
bs.value,
self.cx.error3(bs.span)
);
None
}
},
};
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,
vrr,
tearing,
format,
color_space,
eotf,
brightness,
blend_space,
use_native_gamut: use_native_gamut.despan(),
})
}
}
pub struct OutputsParser<'a>(pub &'a Context<'a>);
impl Parser for OutputsParser<'_> {
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])
}
}
struct BrightnessParser;
#[derive(Debug, Error)]
pub enum BrightnessParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Expected `default`")]
UnexpectedString(String),
}
impl Parser for BrightnessParser {
type Value = Option<f64>;
type Error = BrightnessParserError;
const EXPECTED: &'static [DataType] = &[DataType::Float, DataType::Integer, DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
if string == "default" {
return Ok(None);
}
Err(BrightnessParserError::UnexpectedString(string.to_string()).spanned(span))
}
fn parse_integer(&mut self, _span: Span, integer: i64) -> ParseResult<Self> {
Ok(Some(integer as _))
}
fn parse_float(&mut self, _span: Span, float: f64) -> ParseResult<Self> {
Ok(Some(float))
}
}

View file

@ -0,0 +1,70 @@
use {
crate::{
config::{
OutputMatch,
context::Context,
extractor::{Extractor, ExtractorError, opt, str},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_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 Parser for OutputMatchParser<'_> {
type Value = OutputMatch;
type Error = OutputMatchParserError;
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(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,45 @@
use {
crate::{
config::{
RepeatRate,
context::Context,
extractor::{Extractor, ExtractorError, s32},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum RepeatRateParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct RepeatRateParser<'a>(pub &'a Context<'a>);
impl Parser for RepeatRateParser<'_> {
type Value = RepeatRate;
type Error = RepeatRateParserError;
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 (rate, delay) = ext.extract((s32("rate"), s32("delay")))?;
Ok(RepeatRate {
rate: rate.value,
delay: delay.value,
})
}
}

View file

@ -0,0 +1,208 @@
use {
crate::{
config::{
Action, Shortcut, SimpleCommand,
context::Context,
extractor::{Extractor, ExtractorError, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::{ActionParser, ActionParserError},
modified_keysym::{
ModifiedKeysymParser, ModifiedKeysymParserError, ModifiersParser,
},
},
spanned::SpannedErrorExt,
},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::keyboard::{ModifiedKeySym, mods::Modifiers},
std::collections::HashSet,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ShortcutsParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
ExtractorError(#[from] ExtractorError),
#[error("Could not parse the mod mask")]
ModMask(#[source] ModifiedKeysymParserError),
#[error("Could not parse the action")]
ActionParserError(#[source] ActionParserError),
#[error("Could not parse the latch action")]
LatchError(#[source] ActionParserError),
}
pub struct ShortcutsParser<'a, 'b> {
pub cx: &'a Context<'a>,
pub used_keys: &'b mut HashSet<Spanned<ModifiedKeySym>>,
pub shortcuts: &'b mut Vec<Shortcut>,
}
impl Parser for ShortcutsParser<'_, '_> {
type Value = ();
type Error = ShortcutsParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
for (key, value) in table.iter() {
let Some(keysym) = parse_modified_keysym(self.cx, key) else {
continue;
};
let Some(action) = parse_action(self.cx, &key.value, value) else {
continue;
};
let spanned = keysym.spanned(key.span);
log_used(self.cx, self.used_keys, spanned);
self.shortcuts.push(Shortcut {
mask: Modifiers(!0),
keysym,
action,
latch: None,
});
}
Ok(())
}
}
pub struct ComplexShortcutsParser<'a, 'b> {
pub cx: &'a Context<'a>,
pub used_keys: &'b mut HashSet<Spanned<ModifiedKeySym>>,
pub shortcuts: &'b mut Vec<Shortcut>,
}
impl Parser for ComplexShortcutsParser<'_, '_> {
type Value = ();
type Error = ShortcutsParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
for (key, value) in table.iter() {
let Some(keysym) = parse_modified_keysym(self.cx, key) else {
continue;
};
let shortcut = match value.parse(&mut ComplexShortcutParser {
keysym,
cx: self.cx,
}) {
Ok(v) => v,
Err(e) => {
log::warn!(
"Could not parse shortcut for keysym {}: {}",
key.value,
self.cx.error(e)
);
continue;
}
};
let spanned = keysym.spanned(key.span);
log_used(self.cx, self.used_keys, spanned);
self.shortcuts.push(shortcut);
}
Ok(())
}
}
struct ComplexShortcutParser<'a> {
pub keysym: ModifiedKeySym,
pub cx: &'a Context<'a>,
}
impl Parser for ComplexShortcutParser<'_> {
type Value = Shortcut;
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 ext = Extractor::new(self.cx, span, table);
let (mod_mask_val, action_val, latch_val) =
ext.extract((opt(str("mod-mask")), opt(val("action")), opt(val("latch"))))?;
let mod_mask = match mod_mask_val {
None => Modifiers(!0),
Some(v) => ModifiersParser
.parse_string(v.span, v.value)
.map_spanned_err(ShortcutsParserError::ModMask)?,
};
let action = match action_val {
None => Action::SimpleCommand {
cmd: SimpleCommand::None,
},
Some(v) => v
.parse(&mut ActionParser(self.cx))
.map_spanned_err(ShortcutsParserError::ActionParserError)?,
};
let mut latch = None;
if let Some(v) = latch_val {
latch = Some(
v.parse(&mut ActionParser(self.cx))
.map_spanned_err(ShortcutsParserError::LatchError)?,
);
}
Ok(Shortcut {
mask: mod_mask,
keysym: self.keysym,
action,
latch,
})
}
}
fn parse_action(cx: &Context<'_>, key: &str, value: &Spanned<Value>) -> Option<Action> {
match value.parse(&mut ActionParser(cx)) {
Ok(a) => Some(a),
Err(e) => {
log::warn!("Could not parse action for keysym {key}: {}", cx.error(e));
None
}
}
}
fn parse_modified_keysym(cx: &Context<'_>, key: &Spanned<String>) -> Option<ModifiedKeySym> {
parse_modified_keysym_str(cx, key.span, &key.value)
}
pub fn parse_modified_keysym_str(
cx: &Context<'_>,
span: Span,
value: &str,
) -> Option<ModifiedKeySym> {
match ModifiedKeysymParser.parse_string(span, value) {
Ok(k) => Some(k),
Err(e) => {
log::warn!("Could not parse keysym {}: {}", value, cx.error(e));
None
}
}
}
fn log_used(
cx: &Context<'_>,
used: &mut HashSet<Spanned<ModifiedKeySym>>,
key: Spanned<ModifiedKeySym>,
) {
if let Some(prev) = used.get(&key) {
log::warn!(
"Duplicate key overrides previous definition: {}",
cx.error3(key.span)
);
log::info!("Previous definition here: {}", cx.error3(prev.span));
}
used.insert(key);
}

View file

@ -0,0 +1,44 @@
use {
crate::{
config::{
SimpleIm,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, recover},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum SimpleImParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct SimpleImParser<'a>(pub &'a Context<'a>);
impl Parser for SimpleImParser<'_> {
type Value = SimpleIm;
type Error = SimpleImParserError;
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 (enabled,) = ext.extract((recover(opt(bol("enabled"))),))?;
Ok(SimpleIm {
enabled: enabled.despan(),
})
}
}

View file

@ -0,0 +1,81 @@
use {
crate::{
config::{
Status,
context::Context,
extractor::{Extractor, ExtractorError, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::exec::{ExecParser, ExecParserError},
},
jay_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,78 @@
use {
crate::{
config::{
Tearing,
context::Context,
extractor::{Extractor, ExtractorError, opt, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::video::TearingMode,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum TearingParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct TearingParser<'a>(pub &'a Context<'a>);
impl Parser for TearingParser<'_> {
type Value = Tearing;
type Error = TearingParserError;
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 mode = ext.extract(opt(val("mode")))?;
let mode = mode.and_then(|m| match m.parse(&mut TearingModeParser) {
Ok(m) => Some(m),
Err(e) => {
log::error!("Could not parse mode: {}", self.0.error(e));
None
}
});
Ok(Tearing { mode })
}
}
#[derive(Debug, Error)]
pub enum TearingModeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown mode {0}")]
UnknownMode(String),
}
struct TearingModeParser;
impl Parser for TearingModeParser {
type Value = TearingMode;
type Error = TearingModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let mode = match string {
"never" => TearingMode::NEVER,
"always" => TearingMode::ALWAYS,
"variant1" => TearingMode::VARIANT_1,
"variant2" => TearingMode::VARIANT_2,
"variant3" => TearingMode::VARIANT_3,
_ => return Err(TearingModeParserError::UnknownMode(string.to_string()).spanned(span)),
};
Ok(mode)
}
}

View file

@ -0,0 +1,226 @@
use {
crate::{
config::{
Theme,
context::Context,
extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, s32, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::color::ColorParser,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::theme::BarPosition,
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,
highlight_color,
border_width,
title_height,
bar_height,
font,
title_font,
),
(
bar_font,
bar_position_val,
bar_separator_width,
gap,
floating_titles,
active_border_color,
),
) = 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")),
opt(val("highlight-color")),
recover(opt(s32("border-width"))),
recover(opt(s32("title-height"))),
recover(opt(s32("bar-height"))),
recover(opt(str("font"))),
recover(opt(str("title-font"))),
),
(
recover(opt(str("bar-font"))),
recover(opt(str("bar-position"))),
recover(opt(s32("bar-separator-width"))),
recover(opt(s32("gap"))),
recover(opt(bol("floating-titles"))),
opt(val("active-border-color")),
),
))?;
let (title_gap, corner_radius) = ext.extract((
recover(opt(s32("title-gap"))),
recover(opt(fltorint("corner-radius"))),
))?;
let (
(
tab_active_bg_color,
tab_active_border_color,
tab_inactive_bg_color,
tab_inactive_border_color,
tab_active_text_color,
tab_inactive_text_color,
tab_bar_bg_color,
tab_attention_bg_color,
tab_bar_height,
tab_bar_padding,
),
(
tab_bar_radius,
tab_bar_border_width,
tab_bar_text_padding,
tab_bar_gap,
tab_title_align_val,
),
) = ext.extract((
(
opt(val("tab-active-bg-color")),
opt(val("tab-active-border-color")),
opt(val("tab-inactive-bg-color")),
opt(val("tab-inactive-border-color")),
opt(val("tab-active-text-color")),
opt(val("tab-inactive-text-color")),
opt(val("tab-bar-bg-color")),
opt(val("tab-attention-bg-color")),
recover(opt(s32("tab-bar-height"))),
recover(opt(s32("tab-bar-padding"))),
),
(
recover(opt(s32("tab-bar-radius"))),
recover(opt(s32("tab-bar-border-width"))),
recover(opt(s32("tab-bar-text-padding"))),
recover(opt(s32("tab-bar-gap"))),
recover(opt(str("tab-title-align"))),
),
))?;
macro_rules! color {
($e:expr) => {
match $e {
None => None,
Some(v) => match v.parse(&mut ColorParser) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse a color: {}", self.0.error(e));
None
}
},
}
};
}
let bar_position =
bar_position_val.and_then(|value| match value.value.to_lowercase().as_str() {
"top" => Some(BarPosition::Top),
"bottom" => Some(BarPosition::Bottom),
_ => {
log::warn!(
"Unknown bar position '{}': {}",
value.value,
self.0.error3(value.span)
);
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),
active_border_color: color!(active_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),
highlight_color: color!(highlight_color),
border_width: border_width.despan(),
title_height: title_height.despan(),
bar_height: bar_height.despan(),
font: font.map(|f| f.value.to_string()),
title_font: title_font.map(|f| f.value.to_string()),
bar_font: bar_font.map(|f| f.value.to_string()),
bar_position,
bar_separator_width: bar_separator_width.despan(),
gap: gap.despan(),
floating_titles: floating_titles.despan(),
title_gap: title_gap.despan(),
corner_radius: corner_radius.map(|v| v.value as f32),
tab_active_bg_color: color!(tab_active_bg_color),
tab_active_border_color: color!(tab_active_border_color),
tab_inactive_bg_color: color!(tab_inactive_bg_color),
tab_inactive_border_color: color!(tab_inactive_border_color),
tab_active_text_color: color!(tab_active_text_color),
tab_inactive_text_color: color!(tab_inactive_text_color),
tab_bar_bg_color: color!(tab_bar_bg_color),
tab_attention_bg_color: color!(tab_attention_bg_color),
tab_bar_height: tab_bar_height.despan(),
tab_bar_padding: tab_bar_padding.despan(),
tab_bar_radius: tab_bar_radius.despan(),
tab_bar_border_width: tab_bar_border_width.despan(),
tab_bar_text_padding: tab_bar_text_padding.despan(),
tab_bar_gap: tab_bar_gap.despan(),
tab_title_align: tab_title_align_val.map(|v| v.value.to_string()),
})
}
}

View file

@ -0,0 +1,35 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::toml_span::{Span, SpannedExt},
},
jay_config::window::TileState,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum TileStateParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown tile state `{}`", .0)]
UnknownTileState(String),
}
pub struct TileStateParser;
impl Parser for TileStateParser {
type Value = TileState;
type Error = TileStateParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let ty = match string {
"tiled" => TileState::Tiled,
"floating" => TileState::Floating,
_ => {
return Err(TileStateParserError::UnknownTileState(string.to_owned()).spanned(span));
}
};
Ok(ty)
}
}

View file

@ -0,0 +1,49 @@
use {
crate::{
config::{
UiDrag,
context::Context,
extractor::{Extractor, ExtractorError, bol, int, opt, recover},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::exec::ExecParserError,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum UiDragParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Exec(#[from] ExecParserError),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct UiDragParser<'a>(pub &'a Context<'a>);
impl Parser for UiDragParser<'_> {
type Value = UiDrag;
type Error = UiDragParserError;
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 (enabled, threshold) =
ext.extract((recover(opt(bol("enabled"))), recover(opt(int("threshold")))))?;
Ok(UiDrag {
enabled: enabled.despan(),
threshold: threshold.despan().map(|v| v as i32),
})
}
}

View file

@ -0,0 +1,116 @@
use {
crate::{
config::{
Vrr,
context::Context,
extractor::{Extractor, ExtractorError, opt, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::video::VrrMode,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum VrrParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct VrrParser<'a>(pub &'a Context<'a>);
impl Parser for VrrParser<'_> {
type Value = Vrr;
type Error = VrrParserError;
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 (mode, cursor_hz) = ext.extract((opt(val("mode")), opt(val("cursor-hz"))))?;
let mode = mode.and_then(|m| match m.parse(&mut VrrModeParser) {
Ok(m) => Some(m),
Err(e) => {
log::error!("Could not parse mode: {}", self.0.error(e));
None
}
});
let cursor_hz = cursor_hz.and_then(|m| match m.parse(&mut VrrRateParser) {
Ok(m) => Some(m),
Err(e) => {
log::error!("Could not parse rate: {}", self.0.error(e));
None
}
});
Ok(Vrr { mode, cursor_hz })
}
}
#[derive(Debug, Error)]
pub enum VrrModeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown mode {0}")]
UnknownMode(String),
}
struct VrrModeParser;
impl Parser for VrrModeParser {
type Value = VrrMode;
type Error = VrrModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let mode = match string {
"never" => VrrMode::NEVER,
"always" => VrrMode::ALWAYS,
"variant1" => VrrMode::VARIANT_1,
"variant2" => VrrMode::VARIANT_2,
"variant3" => VrrMode::VARIANT_3,
_ => return Err(VrrModeParserError::UnknownMode(string.to_string()).spanned(span)),
};
Ok(mode)
}
}
#[derive(Debug, Error)]
pub enum VrrRateParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown rate {0}")]
UnknownString(String),
}
struct VrrRateParser;
impl Parser for VrrRateParser {
type Value = f64;
type Error = VrrRateParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Float, DataType::Integer];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
match string {
"none" => Ok(f64::INFINITY),
_ => Err(VrrRateParserError::UnknownString(string.to_string()).spanned(span)),
}
}
fn parse_integer(&mut self, _span: Span, integer: i64) -> ParseResult<Self> {
Ok(integer as _)
}
fn parse_float(&mut self, _span: Span, float: f64) -> ParseResult<Self> {
Ok(float)
}
}

View file

@ -0,0 +1,214 @@
use {
crate::{
config::{
GenericMatch, MatchExactly, WindowMatch,
context::Context,
extractor::{Extractor, ExtractorError, arr, bol, n32, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
client_match::{ClientMatchParser, ClientMatchParserError},
content_type::{ContentTypeParser, ContentTypeParserError},
window_type::{WindowTypeParser, WindowTypeParserError},
},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum WindowMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
WindowTypes(#[from] WindowTypeParserError),
#[error(transparent)]
ClientMatchParserError(#[from] ClientMatchParserError),
#[error(transparent)]
ContentTypes(#[from] ContentTypeParserError),
}
pub struct WindowMatchParser<'a>(pub &'a Context<'a>);
impl Parser for WindowMatchParser<'_> {
type Value = WindowMatch;
type Error = WindowMatchParserError;
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 (
(
name,
not_val,
all_val,
any_val,
exactly_val,
types_val,
client_val,
title,
title_regex,
),
(
app_id,
app_id_regex,
floating,
visible,
urgent,
focused,
fullscreen,
just_mapped,
tag,
tag_regex,
),
(
x_class,
x_class_regex,
x_instance,
x_instance_regex,
x_role,
x_role_regex,
workspace,
workspace_regex,
content_types_val,
),
) = ext.extract((
(
opt(str("name")),
opt(val("not")),
opt(arr("all")),
opt(arr("any")),
opt(val("exactly")),
opt(val("types")),
opt(val("client")),
opt(str("title")),
opt(str("title-regex")),
),
(
opt(str("app-id")),
opt(str("app-id-regex")),
opt(bol("floating")),
opt(bol("visible")),
opt(bol("urgent")),
opt(bol("focused")),
opt(bol("fullscreen")),
opt(bol("just-mapped")),
opt(str("tag")),
opt(str("tag-regex")),
),
(
opt(str("x-class")),
opt(str("x-class-regex")),
opt(str("x-instance")),
opt(str("x-instance-regex")),
opt(str("x-role")),
opt(str("x-role-regex")),
opt(str("workspace")),
opt(str("workspace-regex")),
opt(val("content-types")),
),
))?;
let mut not = None;
if let Some(value) = not_val {
not = Some(Box::new(value.parse(&mut WindowMatchParser(self.0))?));
}
macro_rules! list {
($val:expr) => {{
let mut list = None;
if let Some(value) = $val {
let mut res = vec![];
for value in value.value {
res.push(value.parse(&mut WindowMatchParser(self.0))?);
}
list = Some(res);
}
list
}};
}
let all = list!(all_val);
let any = list!(any_val);
let mut types = None;
if let Some(value) = types_val {
types = Some(value.parse_map(&mut WindowTypeParser)?);
}
let mut exactly = None;
if let Some(value) = exactly_val {
exactly = Some(value.parse(&mut WindowMatchExactlyParser(self.0))?);
}
let mut client = None;
if let Some(value) = client_val {
client = Some(value.parse_map(&mut ClientMatchParser(self.0))?);
}
let mut content_types = None;
if let Some(value) = content_types_val {
content_types = Some(value.parse_map(&mut ContentTypeParser)?);
}
Ok(WindowMatch {
generic: GenericMatch {
name: name.despan_into(),
not,
all,
any,
exactly,
},
title: title.despan_into(),
title_regex: title_regex.despan_into(),
app_id: app_id.despan_into(),
app_id_regex: app_id_regex.despan_into(),
floating: floating.despan(),
visible: visible.despan(),
urgent: urgent.despan(),
focused: focused.despan(),
fullscreen: fullscreen.despan(),
just_mapped: just_mapped.despan(),
tag: tag.despan_into(),
tag_regex: tag_regex.despan_into(),
x_class: x_class.despan_into(),
x_class_regex: x_class_regex.despan_into(),
x_instance: x_instance.despan_into(),
x_instance_regex: x_instance_regex.despan_into(),
x_role: x_role.despan_into(),
x_role_regex: x_role_regex.despan_into(),
workspace: workspace.despan_into(),
workspace_regex: workspace_regex.despan_into(),
types,
client,
content_types,
})
}
}
pub struct WindowMatchExactlyParser<'a>(pub &'a Context<'a>);
impl Parser for WindowMatchExactlyParser<'_> {
type Value = MatchExactly<WindowMatch>;
type Error = WindowMatchParserError;
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 (num, list_val) = ext.extract((n32("num"), arr("list")))?;
let mut list = vec![];
for el in list_val.value {
list.push(el.parse(&mut WindowMatchParser(self.0))?);
}
Ok(MatchExactly {
num: num.value as _,
list,
})
}
}

View file

@ -0,0 +1,122 @@
use {
crate::{
config::{
WindowMatch, WindowRule,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, recover, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::{ActionParser, ActionParserError},
tile_state::TileStateParser,
window_match::{WindowMatchParser, WindowMatchParserError},
},
spanned::SpannedErrorExt,
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum WindowRuleParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] WindowMatchParserError),
#[error(transparent)]
Action(ActionParserError),
#[error(transparent)]
Latch(ActionParserError),
}
pub struct WindowRuleParser<'a>(pub &'a Context<'a>);
impl Parser for WindowRuleParser<'_> {
type Value = WindowRule;
type Error = WindowRuleParserError;
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 (name, match_val, action_val, latch_val, auto_focus, initial_tile_state_val) = ext
.extract((
opt(str("name")),
opt(val("match")),
opt(val("action")),
opt(val("latch")),
recover(opt(bol("auto-focus"))),
opt(val("initial-tile-state")),
))?;
let mut action = None;
if let Some(value) = action_val {
action = Some(
value
.parse(&mut ActionParser(self.0))
.map_spanned_err(WindowRuleParserError::Action)?,
);
}
let mut latch = None;
if let Some(value) = latch_val {
latch = Some(
value
.parse(&mut ActionParser(self.0))
.map_spanned_err(WindowRuleParserError::Latch)?,
);
}
let mut initial_tile_state = None;
if let Some(value) = initial_tile_state_val {
match value.parse(&mut TileStateParser) {
Ok(v) => initial_tile_state = Some(v),
Err(e) => {
log::warn!(
"Could not parse the initial tile state: {}",
self.0.error(e)
);
}
}
}
let match_ = match match_val {
None => WindowMatch::default(),
Some(m) => m.parse_map(&mut WindowMatchParser(self.0))?,
};
Ok(WindowRule {
name: name.despan_into(),
match_,
action,
latch,
auto_focus: auto_focus.despan(),
initial_tile_state,
})
}
}
pub struct WindowRulesParser<'a>(pub &'a Context<'a>);
impl Parser for WindowRulesParser<'_> {
type Value = Vec<WindowRule>;
type Error = WindowRuleParserError;
const EXPECTED: &'static [DataType] = &[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 WindowRuleParser(self.0)) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse window rule: {}", self.0.error(e));
}
}
}
Ok(res)
}
}

View file

@ -0,0 +1,53 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
jay_config::{window, window::WindowType},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum WindowTypeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown window type `{}`", .0)]
UnknownWindowType(String),
}
pub struct WindowTypeParser;
impl Parser for WindowTypeParser {
type Value = WindowType;
type Error = WindowTypeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let ty = match string {
"none" => WindowType(0),
"any" => WindowType(!0),
"container" => window::CONTAINER,
"placeholder" => window::PLACEHOLDER,
"xdg-toplevel" => window::XDG_TOPLEVEL,
"x-window" => window::X_WINDOW,
"client-window" => window::CLIENT_WINDOW,
_ => {
return Err(
WindowTypeParserError::UnknownWindowType(string.to_owned()).spanned(span)
);
}
};
Ok(ty)
}
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut ty = WindowType(0);
for el in array {
ty |= el.parse(&mut WindowTypeParser)?;
}
Ok(ty)
}
}

View file

@ -0,0 +1,32 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
jay_toml::toml_span::{Span, SpannedExt},
},
jay_config::workspace::WorkspaceDisplayOrder,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum WorkspaceDisplayOrderParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown workspace display order {0}")]
Unknown(String),
}
pub struct WorkspaceDisplayOrderParser;
impl Parser for WorkspaceDisplayOrderParser {
type Value = WorkspaceDisplayOrder;
type Error = WorkspaceDisplayOrderParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
match string {
"manual" => Ok(WorkspaceDisplayOrder::Manual),
"sorted" => Ok(WorkspaceDisplayOrder::Sorted),
_ => Err(WorkspaceDisplayOrderParserError::Unknown(string.to_string()).spanned(span)),
}
}
}

View file

@ -0,0 +1,79 @@
use {
crate::{
config::{
Xwayland,
context::Context,
extractor::{Extractor, ExtractorError, bol, opt, recover, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::xwayland::XScalingMode,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum XwaylandParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct XwaylandParser<'a>(pub &'a Context<'a>);
impl Parser for XwaylandParser<'_> {
type Value = Xwayland;
type Error = XwaylandParserError;
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 (enabled, scaling_mode) =
ext.extract((recover(opt(bol("enabled"))), opt(val("scaling-mode"))))?;
let scaling_mode = scaling_mode.and_then(|m| match m.parse(&mut XScalingModeParser) {
Ok(m) => Some(m),
Err(e) => {
log::error!("Could not parse scaling mode: {}", self.0.error(e));
None
}
});
Ok(Xwayland {
enabled: enabled.despan(),
scaling_mode,
})
}
}
#[derive(Debug, Error)]
pub enum XScalingModeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("Unknown mode {0}")]
UnknownMode(String),
}
struct XScalingModeParser;
impl Parser for XScalingModeParser {
type Value = XScalingMode;
type Error = XScalingModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let mode = match string {
"default" => XScalingMode::DEFAULT,
"downscaled" => XScalingMode::DOWNSCALED,
_ => return Err(XScalingModeParserError::UnknownMode(string.to_string()).spanned(span)),
};
Ok(mode)
}
}

View file

@ -0,0 +1 @@
pub use jay_toml::SpannedErrorExt;

View file

@ -0,0 +1,78 @@
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" },
{ type = "exec", exec = "wl-tray-bridge" },
]
[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 = "make-group-h"
alt-v = "make-group-v"
alt-t = "change-group-opposite"
alt-m = "toggle-tab"
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" }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,469 @@
use {
crate::{
ActionExt, State,
config::{ClientMatch, ClientRule, GenericMatch, WindowMatch, WindowRule},
},
ahash::{AHashMap, AHashSet},
jay_config::{
client::{ClientCriterion, ClientMatcher},
window::{WindowCriterion, WindowMatcher},
},
std::{mem::ManuallyDrop, rc::Rc},
};
impl State {
pub fn create_rules<R>(self: &Rc<Self>, rules: &[R]) -> (Vec<MatcherTemp<R>>, RuleMapper<R>)
where
R: Rule,
{
let mut names = AHashMap::new();
for (idx, rule) in rules.iter().enumerate() {
if let Some(name) = rule.name() {
names.insert(name.to_string(), idx);
}
}
let mut mapper = RuleMapper {
state: self.clone(),
names,
pending: Default::default(),
mapped: Default::default(),
};
let mut matchers = vec![];
for idx in 0..rules.len() {
if let Some(matcher) = mapper.map_rule(rules, idx) {
matchers.push(MatcherTemp(matcher));
}
}
(matchers, mapper)
}
}
pub trait Rule: Sized + 'static {
type Match;
type Matcher: Copy + 'static;
type Criterion<'a>;
const NAME_UPPER: &str;
const NAME_LOWER: &str;
fn name(&self) -> Option<&str>;
fn match_(&self) -> &Self::Match;
fn generic(m: &Self::Match) -> &GenericMatch<Self::Match>;
fn map_custom(
state: &Rc<State>,
all: &mut Vec<MatcherTemp<Self>>,
match_: &Self::Match,
) -> Option<()>;
fn create(c: Self::Criterion<'_>) -> Self::Matcher;
fn destroy(m: Self::Matcher);
fn bind(&self, state: &Rc<State>, matcher: Self::Matcher);
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static>;
fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a>;
fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>;
fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>;
fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>;
}
impl Rule for ClientRule {
type Match = ClientMatch;
type Matcher = ClientMatcher;
type Criterion<'a> = ClientCriterion<'a>;
const NAME_UPPER: &str = "Client";
const NAME_LOWER: &str = "client";
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
fn match_(&self) -> &Self::Match {
&self.match_
}
fn generic(m: &Self::Match) -> &GenericMatch<Self::Match> {
&m.generic
}
fn map_custom(
_state: &Rc<State>,
all: &mut Vec<MatcherTemp<Self>>,
match_: &Self::Match,
) -> Option<()> {
let m = |c: ClientCriterion<'_>| MatcherTemp(c.to_matcher());
macro_rules! value_ref {
($ty:ident, $field:ident) => {
if let Some(value) = &match_.$field {
all.push(m(ClientCriterion::$ty(value)));
}
};
}
macro_rules! value {
($ty:ident, $field:ident) => {
if let Some(value) = match_.$field {
all.push(m(ClientCriterion::$ty(value)));
}
};
}
macro_rules! bool {
($ty:ident, $field:ident) => {
if let Some(value) = &match_.$field {
let crit = ClientCriterion::$ty;
let matcher = match value {
false => m(ClientCriterion::Not(&crit)),
true => m(crit),
};
all.push(matcher);
}
};
}
value_ref!(SandboxEngine, sandbox_engine);
value_ref!(SandboxEngineRegex, sandbox_engine_regex);
value_ref!(SandboxAppId, sandbox_app_id);
value_ref!(SandboxAppIdRegex, sandbox_app_id_regex);
value_ref!(SandboxInstanceId, sandbox_instance_id);
value_ref!(SandboxInstanceIdRegex, sandbox_instance_id_regex);
value_ref!(Comm, comm);
value_ref!(CommRegex, comm_regex);
value_ref!(Exe, exe);
value_ref!(ExeRegex, exe_regex);
value!(Uid, uid);
value!(Pid, pid);
bool!(Sandboxed, sandboxed);
bool!(IsXwayland, is_xwayland);
Some(())
}
fn create(c: Self::Criterion<'_>) -> Self::Matcher {
c.to_matcher()
}
fn destroy(m: Self::Matcher) {
m.destroy();
}
fn bind(&self, state: &Rc<State>, matcher: Self::Matcher) {
let state = state.clone();
macro_rules! latch {
($g:ident, $client:ident) => {
let g = $g.clone();
let state = state.clone();
$client.latch(move || {
state.with_client($client.client(), true, || g());
});
};
}
if let Some(action) = &self.action {
let f = action.clone().into_fn(&state);
if let Some(action) = &self.latch {
let g = action.clone().into_rc_fn(&state);
let state = state.clone();
matcher.bind(move |client| {
state.with_client(client.client(), false, &f);
latch!(g, client);
});
} else {
matcher.bind(move |client| {
state.with_client(client.client(), false, &f);
});
}
} else {
if let Some(action) = &self.latch {
let g = action.clone().into_rc_fn(&state);
matcher.bind(move |client| {
latch!(g, client);
});
}
}
}
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> {
ClientCriterion::Matcher(m)
}
fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a> {
ClientCriterion::Not(m)
}
fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
ClientCriterion::All(m)
}
fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
ClientCriterion::Any(m)
}
fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
ClientCriterion::Exactly(n, m)
}
}
impl Rule for WindowRule {
type Match = WindowMatch;
type Matcher = WindowMatcher;
type Criterion<'a> = WindowCriterion<'a>;
const NAME_UPPER: &str = "Window";
const NAME_LOWER: &str = "window";
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
fn match_(&self) -> &Self::Match {
&self.match_
}
fn generic(m: &Self::Match) -> &GenericMatch<Self::Match> {
&m.generic
}
fn map_custom(
state: &Rc<State>,
all: &mut Vec<MatcherTemp<Self>>,
match_: &Self::Match,
) -> Option<()> {
let m = |c: WindowCriterion<'_>| MatcherTemp(c.to_matcher());
macro_rules! value {
($ty:ident, $field:ident) => {
if let Some(value) = &match_.$field {
all.push(m(WindowCriterion::$ty(value)));
}
};
}
macro_rules! bool {
($ty:ident, $field:ident) => {
if let Some(value) = &match_.$field {
let crit = WindowCriterion::$ty;
let matcher = match value {
false => m(WindowCriterion::Not(&crit)),
true => m(crit),
};
all.push(matcher);
}
};
}
if let Some(value) = &match_.types {
all.push(m(WindowCriterion::Types(*value)));
}
if let Some(value) = &match_.client {
let mut mapper = state.persistent.client_rule_mapper.borrow_mut();
let mapper = mapper.as_mut()?;
let matcher = mapper.map_temporary_match(&[], value)?;
all.push(m(WindowCriterion::Client(&ClientCriterion::Matcher(
matcher.0,
))));
}
value!(Title, title);
value!(TitleRegex, title_regex);
value!(AppId, app_id);
value!(AppIdRegex, app_id_regex);
value!(Tag, tag);
value!(TagRegex, tag_regex);
value!(XClass, x_class);
value!(XClassRegex, x_class_regex);
value!(XInstance, x_instance);
value!(XInstanceRegex, x_instance_regex);
value!(XRole, x_role);
value!(XRoleRegex, x_role_regex);
value!(WorkspaceName, workspace);
value!(WorkspaceNameRegex, workspace_regex);
bool!(Floating, floating);
bool!(Visible, visible);
bool!(Urgent, urgent);
bool!(Fullscreen, fullscreen);
bool!(JustMapped, just_mapped);
if let Some(value) = match_.focused {
let crit = WindowCriterion::Focus(state.persistent.seat);
let matcher = match value {
false => m(WindowCriterion::Not(&crit)),
true => m(crit),
};
all.push(matcher);
}
if let Some(value) = &match_.content_types {
all.push(m(WindowCriterion::ContentTypes(*value)));
}
Some(())
}
fn create(c: Self::Criterion<'_>) -> Self::Matcher {
c.to_matcher()
}
fn destroy(m: Self::Matcher) {
m.destroy();
}
fn bind(&self, state: &Rc<State>, matcher: Self::Matcher) {
let state = state.clone();
macro_rules! latch {
($g:ident, $client:ident, $win:ident) => {
let g = $g.clone();
let state = state.clone();
$win.latch(move || {
state.with_client($client, true, || {
state.with_window(*$win, true, || g());
});
});
};
}
if let Some(action) = &self.action {
let f = action.clone().into_fn(&state);
if let Some(action) = &self.latch {
let g = action.clone().into_rc_fn(&state);
matcher.bind(move |win| {
let client = win.client();
state.with_client(client, false, || {
state.with_window(*win, false, &f);
});
latch!(g, client, win);
});
} else {
matcher.bind(move |win| {
let client = win.client();
state.with_client(client, false, || {
state.with_window(*win, false, &f);
});
});
}
} else {
if let Some(action) = &self.latch {
let g = action.clone().into_rc_fn(&state);
matcher.bind(move |win| {
let client = win.client();
latch!(g, client, win);
});
}
}
if let Some(auto_focus) = self.auto_focus {
matcher.set_auto_focus(auto_focus);
}
if let Some(tile_state) = self.initial_tile_state {
matcher.set_initial_tile_state(tile_state);
}
}
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> {
WindowCriterion::Matcher(m)
}
fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a> {
WindowCriterion::Not(m)
}
fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
WindowCriterion::All(m)
}
fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
WindowCriterion::Any(m)
}
fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
WindowCriterion::Exactly(n, m)
}
}
pub struct RuleMapper<R>
where
R: Rule,
{
state: Rc<State>,
names: AHashMap<String, usize>,
pending: AHashSet<usize>,
mapped: AHashMap<usize, R::Matcher>,
}
pub struct MatcherTemp<R>(R::Matcher)
where
R: Rule;
impl<R> Drop for MatcherTemp<R>
where
R: Rule,
{
fn drop(&mut self) {
R::destroy(self.0);
}
}
impl<R> RuleMapper<R>
where
R: Rule,
{
fn map_rule(&mut self, rules: &[R], idx: usize) -> Option<R::Matcher> {
if let Some(matcher) = self.mapped.get(&idx) {
return Some(*matcher);
}
if !self.pending.insert(idx) {
if let Some(name) = rules.get(idx).and_then(|r| r.name()) {
log::error!("{} rule `{name}` has a loop", R::NAME_UPPER);
}
return None;
}
let rule = &rules[idx];
let matcher = self.map_match(rules, rule.match_())?;
self.mapped.insert(idx, matcher);
rule.bind(&self.state, matcher);
Some(matcher)
}
fn map_temporary_match(&mut self, rules: &[R], matcher: &R::Match) -> Option<MatcherTemp<R>> {
self.map_match(rules, matcher).map(MatcherTemp)
}
fn map_match(&mut self, rules: &[R], matcher: &R::Match) -> Option<R::Matcher> {
let mut all = vec![];
self.map_generic_match(rules, &mut all, R::generic(matcher))?;
R::map_custom(&self.state, &mut all, matcher)?;
if all.len() == 1 {
return Some(ManuallyDrop::new(all.pop().unwrap()).0);
}
let all: Vec<_> = all.iter().map(|m| R::gen_matcher(m.0)).collect();
Some(R::create(R::gen_all(&all)))
}
fn map_generic_match(
&mut self,
rules: &[R],
all: &mut Vec<MatcherTemp<R>>,
matcher: &GenericMatch<R::Match>,
) -> Option<()> {
let m = |c: R::Criterion<'_>| MatcherTemp(R::create(c));
if let Some(name) = &matcher.name {
let Some(&idx) = self.names.get(&**name) else {
log::error!("There is no {} rule named `{name}`", R::NAME_LOWER);
return None;
};
let matcher = self.map_rule(rules, idx)?;
all.push(m(R::gen_matcher(matcher)));
}
if let Some(not) = &matcher.not {
let matcher = self.map_temporary_match(rules, not)?;
all.push(m(R::gen_not(&R::gen_matcher(matcher.0))));
}
if let Some(list) = &matcher.all {
for match_ in list {
all.push(self.map_temporary_match(rules, match_)?);
}
}
if let Some(list) = &matcher.any {
let mut any = vec![];
for match_ in list {
any.push(self.map_temporary_match(rules, match_)?);
}
let any: Vec<_> = any.iter().map(|m| R::gen_matcher(m.0)).collect();
all.push(m(R::gen_any(&any)));
}
if let Some(exactly) = &matcher.exactly {
let mut list = vec![];
for match_ in &exactly.list {
list.push(self.map_temporary_match(rules, match_)?);
}
let list: Vec<_> = list.iter().map(|m| R::gen_matcher(m.0)).collect();
all.push(m(R::gen_exactly(exactly.num, &list)))
}
Some(())
}
}

View file

@ -0,0 +1,276 @@
use {
crate::{
ActionExt, State,
config::{Action, InputMode, Shortcut, SimpleCommand},
},
ahash::{AHashMap, AHashSet},
jay_config::keyboard::{ModifiedKeySym, mods::Modifiers},
std::{
cell::{Cell, RefCell},
collections::hash_map::Entry,
rc::Rc,
},
};
#[derive(Default)]
pub struct ModeState {
latched: Cell<bool>,
stack: RefCell<Vec<Rc<ConvertedShortcuts>>>,
slots: RefCell<AHashMap<String, Rc<ModeSlot>>>,
diffs: RefCell<AHashMap<[*const ConvertedShortcuts; 2], Rc<Vec<ModeDiff>>>>,
current: RefCell<Rc<ConvertedShortcuts>>,
}
impl ModeState {
pub fn clear(&self) {
self.slots.borrow_mut().clear();
self.stack.borrow_mut().clear();
self.diffs.borrow_mut().clear();
*self.current.borrow_mut() = Default::default();
}
}
pub type ConvertedShortcuts = AHashMap<ModifiedKeySym, ConvertedShortcut>;
#[derive(Clone)]
pub struct ConvertedShortcut {
mask: Modifiers,
shortcut: Rc<dyn Fn()>,
}
#[derive(Default)]
pub struct ModeSlot {
pub mode: RefCell<Option<Rc<ConvertedShortcuts>>>,
}
enum ModeDiff {
Bind(ModifiedKeySym, Modifiers, Rc<dyn Fn()>),
Unbind(ModifiedKeySym),
}
impl PartialEq for ConvertedShortcut {
fn eq(&self, other: &Self) -> bool {
if self.mask != other.mask {
return false;
}
Rc::ptr_eq(&self.shortcut, &other.shortcut)
}
}
impl State {
pub fn get_mode_slot(&self, name: &str) -> Rc<ModeSlot> {
let state = &self.persistent.mode_state;
state
.slots
.borrow_mut()
.entry(name.to_string())
.or_default()
.clone()
}
pub fn clear_modes_after_reload(&self) {
let state = &self.persistent.mode_state;
state.slots.borrow_mut().clear();
state.diffs.borrow_mut().clear();
}
pub fn init_modes(
self: &Rc<Self>,
shortcuts: &[Shortcut],
modes: &AHashMap<String, InputMode>,
) {
let state = &self.persistent.mode_state;
let base = self.convert_shortcuts(shortcuts);
let stack = &mut *state.stack.borrow_mut();
stack.clear();
stack.push(base.clone());
self.convert_modes(&base, modes);
self.apply_shortcuts(&base);
state.latched.set(false);
}
pub fn set_mode(&self, new: &Rc<ConvertedShortcuts>, latch: bool) {
let state = &self.persistent.mode_state;
self.cancel_mode_latch();
self.apply_shortcuts(new);
let stack = &mut *state.stack.borrow_mut();
stack.push(new.clone());
if latch {
state.latched.set(true);
}
}
pub fn pop_mode(&self, pop: bool) {
let state = &self.persistent.mode_state;
let stack = &mut *state.stack.borrow_mut();
if stack.len() < 1 + pop as usize {
log::error!("Mode stack is empty");
return;
}
self.cancel_mode_latch();
if pop {
stack.pop();
} else {
stack.truncate(1);
}
let new = stack.last().unwrap();
self.apply_shortcuts(new);
}
pub fn cancel_mode_latch(&self) {
let state = &self.persistent.mode_state;
if !state.latched.take() {
return;
}
let stack = &mut *state.stack.borrow_mut();
if stack.len() < 2 {
log::error!("Mode is latched but mode stack is empty");
return;
}
let _ = stack.pop();
let new = stack.last().unwrap();
self.apply_shortcuts(new);
}
pub fn convert_modes(
self: &Rc<Self>,
base: &ConvertedShortcuts,
modes: &AHashMap<String, InputMode>,
) {
let mut pending = AHashSet::new();
let mut out = AHashMap::new();
for (name, mode) in modes {
if !out.contains_key(name) {
self.convert_mode(&mut out, &mut pending, base, modes, name, mode);
}
}
}
fn convert_mode<'a>(
self: &Rc<Self>,
out: &'a mut AHashMap<String, Rc<ConvertedShortcuts>>,
pending: &mut AHashSet<String>,
base: &ConvertedShortcuts,
modes: &AHashMap<String, InputMode>,
mode_name: &String,
mode: &InputMode,
) -> Option<&'a ConvertedShortcuts> {
if !pending.insert(mode_name.clone()) {
log::warn!("Detected loop while converting input mode `{mode_name}`");
return None;
}
let mut shortcuts = None;
if let Some(parent) = &mode.parent {
match out.get(parent) {
Some(c) => shortcuts = Some((**c).clone()),
None => match modes.get(parent) {
None => {
log::warn!("Input mode `{parent}` does not exist");
}
Some(p) => {
if let Some(p) = self.convert_mode(out, pending, base, modes, parent, p) {
shortcuts = Some(p.clone());
}
}
},
}
}
let mut shortcuts = shortcuts.unwrap_or_else(|| base.clone());
self.convert_shortcuts_(&mode.shortcuts, &mut shortcuts);
let shortcuts = Rc::new(shortcuts);
*self.get_mode_slot(mode_name).mode.borrow_mut() = Some(shortcuts.clone());
let res = out.entry(mode_name.clone()).insert_entry(shortcuts);
Some(res.into_mut())
}
pub fn convert_shortcuts<'a>(
self: &Rc<Self>,
shortcuts: impl IntoIterator<Item = &'a Shortcut>,
) -> Rc<ConvertedShortcuts> {
let mut dst = ConvertedShortcuts::new();
self.convert_shortcuts_(shortcuts, &mut dst);
Rc::new(dst)
}
fn convert_shortcuts_<'a>(
self: &Rc<Self>,
shortcuts: impl IntoIterator<Item = &'a Shortcut>,
dst: &mut ConvertedShortcuts,
) {
for sc in shortcuts {
match self.convert_shortcut(sc.clone()) {
None => dst.remove(&sc.keysym),
Some(cs) => dst.insert(sc.keysym, cs),
};
}
}
fn convert_shortcut(self: &Rc<Self>, shortcut: Shortcut) -> Option<ConvertedShortcut> {
if let Action::SimpleCommand {
cmd: SimpleCommand::None,
} = shortcut.action
&& shortcut.latch.is_none()
{
return None;
}
let mut f = shortcut.action.into_shortcut_fn(self);
if let Some(l) = shortcut.latch {
let l = l.into_rc_fn(self);
let s = self.persistent.seat;
f = Rc::new(move || {
f();
let l = l.clone();
s.latch(move || l());
});
}
Some(ConvertedShortcut {
mask: shortcut.mask,
shortcut: f,
})
}
pub fn apply_shortcuts(&self, new: &Rc<ConvertedShortcuts>) {
let state = &self.persistent.mode_state;
let current = &mut *state.current.borrow_mut();
let diffs = self.get_or_create_mode_diffs(current, new);
let seat = &self.persistent.seat;
for diff in &*diffs {
match diff {
ModeDiff::Bind(key, mask, f) => {
let f = f.clone();
seat.bind_masked(*mask, *key, move || f());
}
ModeDiff::Unbind(key) => {
seat.unbind(*key);
}
}
}
*current = new.clone();
}
fn get_or_create_mode_diffs(
&self,
old: &Rc<ConvertedShortcuts>,
new: &Rc<ConvertedShortcuts>,
) -> Rc<Vec<ModeDiff>> {
let state = &self.persistent.mode_state;
let diffs = &mut *state.diffs.borrow_mut();
match diffs.entry([Rc::as_ptr(old), Rc::as_ptr(new)]) {
Entry::Occupied(o) => o.get().clone(),
Entry::Vacant(v) => {
let mut diffs = vec![];
for (key, sc) in new.iter() {
if old.get(key) != Some(sc) {
diffs.push(ModeDiff::Bind(*key, sc.mask, sc.shortcut.clone()));
}
}
for key in old.keys() {
if !new.contains_key(key) {
diffs.push(ModeDiff::Unbind(*key));
}
}
v.insert(Rc::new(diffs)).clone()
}
}
}
}