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