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, }, }, 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>, ) -> ParseResult { let mut ext = Extractor::new(self.cx, span, table); let ( ( tag, match_val, accel_profile, accel_speed, tap_enabled, tap_drag_enabled, tap_drag_lock_enabled, left_handed, natural_scrolling, px_per_wheel_scroll, ), ( transform_matrix, 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>, 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; type Error = InputParserError; const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array]; fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { let mut res = vec![]; for el in array { match el.parse(&mut InputParser { cx: self.0, 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>, ) -> ParseResult { 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]) -> ParseResult { if array.len() != 2 { return Err(InputParserError::TwoRows.spanned(span)); } Ok([ array[0].parse(&mut TransformMatrixRowParser)?, array[1].parse(&mut TransformMatrixRowParser)?, ]) } } struct TransformMatrixRowParser; impl Parser for TransformMatrixRowParser { type Value = [f64; 2]; type Error = InputParserError; const EXPECTED: &'static [DataType] = &[DataType::Array]; fn parse_array(&mut self, span: Span, array: &[Spanned]) -> ParseResult { if array.len() != 2 { return Err(InputParserError::TwoColumns.spanned(span)); } let extract = |v: &Spanned| match v.value { Value::Float(f) => Ok(f), Value::Integer(f) => Ok(f as _), _ => Err(InputParserError::Float.spanned(v.span)), }; Ok([extract(&array[0])?, extract(&array[1])?]) } } 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]) -> ParseResult { 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]) -> ParseResult { if array.len() != 3 { return Err(InputParserError::CaliThreeColumns.spanned(span)); } let extract = |v: &Spanned| 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])?, ]) } }