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}, }, ahash::AHashMap, jay_config::{ input::{acceleration::AccelProfile, SwitchEvent}, keyboard::{mods::Modifiers, Keymap, ModifiedKeySym}, logging::LogLevel, status::MessageFormat, theme::Color, video::{GfxApi, Transform}, Axis, Direction, Workspace, }, 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, Forward(bool), } #[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, }, MoveToWorkspace { name: String, }, 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, }, MoveToOutput { workspace: Option, output: OutputMatch, }, SetRepeatRate { rate: RepeatRate, }, } #[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 highlight_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]>, pub keymap: Option, pub switch_actions: AHashMap, pub output: Option>, } #[derive(Debug, Clone)] pub struct Exec { pub prog: String, pub args: Vec, pub envs: Vec<(String, String)>, pub privileged: bool, } #[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 RepeatRate { pub rate: i32, pub delay: i32, } #[derive(Debug, Clone)] pub struct Shortcut { pub mask: Modifiers, pub keysym: ModifiedKeySym, pub action: Action, pub latch: Option, } #[derive(Debug, Clone)] pub struct Config { pub keymap: Option, pub repeat_rate: Option, pub shortcuts: Vec, 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, pub explicit_sync_enabled: Option, pub focus_follows_mouse: bool, } #[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(); }