workspace: move crates under crates
This commit is contained in:
parent
0016bc8cf0
commit
6393fdf3c0
354 changed files with 102 additions and 102 deletions
121
crates/toml-config/src/config.rs
Normal file
121
crates/toml-config/src/config.rs
Normal 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"));
|
||||
}
|
||||
66
crates/toml-config/src/config/context.rs
Normal file
66
crates/toml-config/src/config/context.rs
Normal 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
89
crates/toml-config/src/config/error.rs
Normal file
89
crates/toml-config/src/config/error.rs
Normal 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)
|
||||
}
|
||||
283
crates/toml-config/src/config/extractor.rs
Normal file
283
crates/toml-config/src/config/extractor.rs
Normal 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,);
|
||||
520
crates/toml-config/src/config/keycodes.rs
Normal file
520
crates/toml-config/src/config/keycodes.rs
Normal 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,
|
||||
};
|
||||
3
crates/toml-config/src/config/parser.rs
Normal file
3
crates/toml-config/src/config/parser.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub use jay_toml::value_parser::{
|
||||
DataType, ParseResult, Parser, UnexpectedDataType,
|
||||
};
|
||||
73
crates/toml-config/src/config/parsers.rs
Normal file
73
crates/toml-config/src/config/parsers.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
588
crates/toml-config/src/config/parsers/action.rs
Normal file
588
crates/toml-config/src/config/parsers/action.rs
Normal 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
|
||||
}
|
||||
}
|
||||
78
crates/toml-config/src/config/parsers/actions.rs
Normal file
78
crates/toml-config/src/config/parsers/actions.rs
Normal 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);
|
||||
}
|
||||
99
crates/toml-config/src/config/parsers/animations.rs
Normal file
99
crates/toml-config/src/config/parsers/animations.rs
Normal 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))
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
160
crates/toml-config/src/config/parsers/client_match.rs
Normal file
160
crates/toml-config/src/config/parsers/client_match.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
104
crates/toml-config/src/config/parsers/client_rule.rs
Normal file
104
crates/toml-config/src/config/parsers/client_rule.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
57
crates/toml-config/src/config/parsers/color.rs
Normal file
57
crates/toml-config/src/config/parsers/color.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
44
crates/toml-config/src/config/parsers/color_management.rs
Normal file
44
crates/toml-config/src/config/parsers/color_management.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
629
crates/toml-config/src/config/parsers/config.rs
Normal file
629
crates/toml-config/src/config/parsers/config.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
83
crates/toml-config/src/config/parsers/connector.rs
Normal file
83
crates/toml-config/src/config/parsers/connector.rs
Normal 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])
|
||||
}
|
||||
}
|
||||
57
crates/toml-config/src/config/parsers/connector_match.rs
Normal file
57
crates/toml-config/src/config/parsers/connector_match.rs
Normal 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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
53
crates/toml-config/src/config/parsers/content_type.rs
Normal file
53
crates/toml-config/src/config/parsers/content_type.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
129
crates/toml-config/src/config/parsers/drm_device.rs
Normal file
129
crates/toml-config/src/config/parsers/drm_device.rs
Normal 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])
|
||||
}
|
||||
}
|
||||
74
crates/toml-config/src/config/parsers/drm_device_match.rs
Normal file
74
crates/toml-config/src/config/parsers/drm_device_match.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
42
crates/toml-config/src/config/parsers/env.rs
Normal file
42
crates/toml-config/src/config/parsers/env.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
125
crates/toml-config/src/config/parsers/exec.rs
Normal file
125
crates/toml-config/src/config/parsers/exec.rs
Normal 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))
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
44
crates/toml-config/src/config/parsers/float.rs
Normal file
44
crates/toml-config/src/config/parsers/float.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
48
crates/toml-config/src/config/parsers/focus_history.rs
Normal file
48
crates/toml-config/src/config/parsers/focus_history.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
67
crates/toml-config/src/config/parsers/format.rs
Normal file
67
crates/toml-config/src/config/parsers/format.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
34
crates/toml-config/src/config/parsers/gfx_api.rs
Normal file
34
crates/toml-config/src/config/parsers/gfx_api.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
99
crates/toml-config/src/config/parsers/idle.rs
Normal file
99
crates/toml-config/src/config/parsers/idle.rs
Normal 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(),
|
||||
)
|
||||
}
|
||||
393
crates/toml-config/src/config/parsers/input.rs
Normal file
393
crates/toml-config/src/config/parsers/input.rs
Normal 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])?,
|
||||
])
|
||||
}
|
||||
}
|
||||
98
crates/toml-config/src/config/parsers/input_match.rs
Normal file
98
crates/toml-config/src/config/parsers/input_match.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
119
crates/toml-config/src/config/parsers/input_mode.rs
Normal file
119
crates/toml-config/src/config/parsers/input_mode.rs
Normal 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());
|
||||
}
|
||||
197
crates/toml-config/src/config/parsers/keymap.rs
Normal file
197
crates/toml-config/src/config/parsers/keymap.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
44
crates/toml-config/src/config/parsers/libei.rs
Normal file
44
crates/toml-config/src/config/parsers/libei.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
37
crates/toml-config/src/config/parsers/log_level.rs
Normal file
37
crates/toml-config/src/config/parsers/log_level.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
61
crates/toml-config/src/config/parsers/mark_id.rs
Normal file
61
crates/toml-config/src/config/parsers/mark_id.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
47
crates/toml-config/src/config/parsers/mode.rs
Normal file
47
crates/toml-config/src/config/parsers/mode.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
107
crates/toml-config/src/config/parsers/modified_keysym.rs
Normal file
107
crates/toml-config/src/config/parsers/modified_keysym.rs
Normal 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)
|
||||
}
|
||||
287
crates/toml-config/src/config/parsers/output.rs
Normal file
287
crates/toml-config/src/config/parsers/output.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
70
crates/toml-config/src/config/parsers/output_match.rs
Normal file
70
crates/toml-config/src/config/parsers/output_match.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
45
crates/toml-config/src/config/parsers/repeat_rate.rs
Normal file
45
crates/toml-config/src/config/parsers/repeat_rate.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
208
crates/toml-config/src/config/parsers/shortcuts.rs
Normal file
208
crates/toml-config/src/config/parsers/shortcuts.rs
Normal 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);
|
||||
}
|
||||
44
crates/toml-config/src/config/parsers/simple_im.rs
Normal file
44
crates/toml-config/src/config/parsers/simple_im.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
81
crates/toml-config/src/config/parsers/status.rs
Normal file
81
crates/toml-config/src/config/parsers/status.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
78
crates/toml-config/src/config/parsers/tearing.rs
Normal file
78
crates/toml-config/src/config/parsers/tearing.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
226
crates/toml-config/src/config/parsers/theme.rs
Normal file
226
crates/toml-config/src/config/parsers/theme.rs
Normal 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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
35
crates/toml-config/src/config/parsers/tile_state.rs
Normal file
35
crates/toml-config/src/config/parsers/tile_state.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
49
crates/toml-config/src/config/parsers/ui_drag.rs
Normal file
49
crates/toml-config/src/config/parsers/ui_drag.rs
Normal 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
116
crates/toml-config/src/config/parsers/vrr.rs
Normal file
116
crates/toml-config/src/config/parsers/vrr.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
214
crates/toml-config/src/config/parsers/window_match.rs
Normal file
214
crates/toml-config/src/config/parsers/window_match.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
122
crates/toml-config/src/config/parsers/window_rule.rs
Normal file
122
crates/toml-config/src/config/parsers/window_rule.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
53
crates/toml-config/src/config/parsers/window_type.rs
Normal file
53
crates/toml-config/src/config/parsers/window_type.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
79
crates/toml-config/src/config/parsers/xwayland.rs
Normal file
79
crates/toml-config/src/config/parsers/xwayland.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
1
crates/toml-config/src/config/spanned.rs
Normal file
1
crates/toml-config/src/config/spanned.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub use jay_toml::SpannedErrorExt;
|
||||
78
crates/toml-config/src/default-config.toml
Normal file
78
crates/toml-config/src/default-config.toml
Normal 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" }
|
||||
1857
crates/toml-config/src/lib.rs
Normal file
1857
crates/toml-config/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
469
crates/toml-config/src/rules.rs
Normal file
469
crates/toml-config/src/rules.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
276
crates/toml-config/src/shortcuts.rs
Normal file
276
crates/toml-config/src/shortcuts.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue