mod context; pub mod error; mod extractor; mod keycodes; mod parser; mod parsers; mod spanned; mod value; pub use crate::config::parsers::input_mode::InputMode; use { crate::{ config::{ context::Context, parsers::{ color_management::ColorManagement, config::{ConfigParser, ConfigParserError}, float::Float, focus_history::FocusHistory, }, }, toml::{self}, }, ahash::AHashMap, jay_config::{ Direction, Workspace, client::ClientCapabilities, input::{ FallbackOutputMode, LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile, clickmethod::ClickMethod, }, keyboard::{Keymap, ModifiedKeySym, mods::Modifiers, syms::KeySym}, logging::LogLevel, status::MessageFormat, theme::{BarPosition, Color}, video::{BlendSpace, ColorSpace, Eotf, Format, GfxApi, TearingMode, Transform, VrrMode}, window::{ContentType, TileState, WindowType}, workspace::WorkspaceDisplayOrder, xwayland::XScalingMode, }, std::{ cell::RefCell, error::Error, fmt::{Display, Formatter}, rc::Rc, 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, ToggleFloating, SetFloating(bool), ToggleFullscreen, SetFullscreen(bool), Forward(bool), EnableWindowManagement(bool), SetFloatAboveFullscreen(bool), ToggleFloatAboveFullscreen, SetFloatPinned(bool), ToggleFloatPinned, KillClient, ShowBar(bool), ToggleBar, ShowTitles(bool), ToggleTitles, FloatTitles(bool), ToggleFloatTitles, FocusHistory(Timeline), FocusLayerRel(LayerDirection), FocusTiles, ToggleFocusFloatTiled, CreateMark, JumpToMark, PopMode(bool), EnableSimpleIm(bool), ToggleSimpleImEnabled, ReloadSimpleIm, EnableUnicodeInput, WarpMouseToFocus, ToggleTab, MakeGroupH, MakeGroupV, MakeGroupTab, ChangeGroupOpposite, Equalize, EqualizeRecursive, MoveTabLeft, MoveTabRight, SetAutotile(bool), ToggleAutotile, } #[derive(Debug, Clone)] #[expect(clippy::enum_variant_names)] pub enum Action { ConfigureConnector { con: ConfigConnector, }, ConfigureDirectScanout { enabled: bool, }, ConfigureDrmDevice { dev: ConfigDrmDevice, }, ConfigureIdle { idle: Option, grace_period: Option, }, ConfigureInput { input: Box, }, 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: Box, }, SetStatus { status: Option, }, SetTheme { theme: Box, }, ShowWorkspace { name: String, output: Option, }, SimpleCommand { cmd: SimpleCommand, }, SwitchToVt { num: u32, }, UnsetEnv { env: Vec, }, MoveToOutput { workspace: Option, output: Option, direction: Option, }, SetRepeatRate { rate: RepeatRate, }, DefineAction { name: String, action: Box, }, UndefineAction { name: String, }, NamedAction { name: String, }, CreateMark(u32), JumpToMark(u32), CopyMark(u32, u32), SetMode { name: String, latch: bool, }, CreateVirtualOutput { name: String, }, RemoveVirtualOutput { name: String, }, Resize { dx1: i32, dy1: i32, dx2: i32, dy2: i32, }, } #[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 active_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 bar_height: Option, pub font: Option, pub title_font: Option, pub bar_font: Option, pub bar_position: Option, pub bar_separator_width: Option, pub gap: Option, pub floating_titles: Option, pub title_gap: Option, pub corner_radius: Option, pub tab_active_bg_color: Option, pub tab_active_border_color: Option, pub tab_inactive_bg_color: Option, pub tab_inactive_border_color: Option, pub tab_active_text_color: Option, pub tab_inactive_text_color: Option, pub tab_bar_bg_color: Option, pub tab_attention_bg_color: Option, pub tab_bar_height: Option, pub tab_bar_padding: Option, pub tab_bar_radius: Option, pub tab_bar_border_width: Option, pub tab_bar_text_padding: Option, pub tab_bar_gap: Option, pub tab_title_align: Option, } #[derive(Debug, Clone)] pub struct Status { pub format: MessageFormat, pub exec: Exec, pub separator: Option, } #[derive(Debug, Clone, Default)] pub struct UiDrag { pub enabled: Option, pub threshold: Option, } #[derive(Debug, Clone, Default)] pub struct Animations { pub enabled: Option, pub duration_ms: Option, pub style: Option, pub curve: Option, } #[derive(Debug, Clone, PartialEq)] pub enum AnimationCurveConfig { Preset(String), CubicBezier([f32; 4]), } #[derive(Debug, Clone)] pub enum OutputMatch { Any(Vec), All { name: Option, connector: Option, serial_number: Option, manufacturer: Option, model: Option, }, } #[derive(Default, Debug, Clone)] pub struct GenericMatch { pub name: Option, pub not: Option>, pub all: Option>, pub any: Option>, pub exactly: Option>, } #[derive(Debug, Clone)] pub struct MatchExactly { pub num: usize, pub list: Vec, } #[derive(Debug, Clone)] pub struct ClientRule { pub name: Option, pub match_: ClientMatch, pub action: Option, pub latch: Option, pub capabilities: Option, pub bounding_capabilities: Option, } #[derive(Default, Debug, Clone)] pub struct ClientMatch { pub generic: GenericMatch, pub sandbox_engine: Option, pub sandbox_engine_regex: Option, pub sandbox_app_id: Option, pub sandbox_app_id_regex: Option, pub sandbox_instance_id: Option, pub sandbox_instance_id_regex: Option, pub sandboxed: Option, pub uid: Option, pub pid: Option, pub is_xwayland: Option, pub comm: Option, pub comm_regex: Option, pub exe: Option, pub exe_regex: Option, pub tag: Option, pub tag_regex: Option, } #[derive(Debug, Clone)] pub struct WindowRule { pub name: Option, pub match_: WindowMatch, pub action: Option, pub latch: Option, pub auto_focus: Option, pub initial_tile_state: Option, } #[derive(Default, Debug, Clone)] pub struct WindowMatch { pub generic: GenericMatch, pub types: Option, pub client: Option, pub title: Option, pub title_regex: Option, pub app_id: Option, pub app_id_regex: Option, pub floating: Option, pub visible: Option, pub urgent: Option, pub focused: Option, pub fullscreen: Option, pub just_mapped: Option, pub tag: Option, pub tag_regex: Option, pub x_class: Option, pub x_class_regex: Option, pub x_instance: Option, pub x_instance_regex: Option, pub x_role: Option, pub x_role_regex: Option, pub workspace: Option, pub workspace_regex: Option, pub content_types: 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, pub vrr: Option, pub tearing: Option, pub format: Option, pub color_space: Option, pub eotf: Option, pub brightness: Option>, pub blend_space: Option, pub use_native_gamut: 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 click_method: Option, pub middle_button_emulation: Option, pub px_per_wheel_scroll: Option, pub transform_matrix: Option<[[f64; 2]; 2]>, pub keymap: Option, pub switch_actions: AHashMap, pub output: Option>, pub calibration_matrix: Option<[[f32; 3]; 2]>, } #[derive(Debug, Clone)] pub struct Exec { pub prog: String, pub args: Vec, pub envs: Vec<(String, String)>, pub privileged: bool, pub tag: Option, } #[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, pub flip_margin_ms: 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 Vrr { pub mode: Option, pub cursor_hz: Option, } #[derive(Debug, Clone)] pub struct SimpleIm { pub enabled: Option, } #[derive(Debug, Clone)] pub struct Xwayland { pub enabled: Option, pub scaling_mode: Option, } #[derive(Debug, Clone)] pub struct Tearing { pub mode: Option, } #[derive(Debug, Clone, Default)] pub struct Libei { pub enable_socket: Option, } #[derive(Debug, Clone)] pub struct Shortcut { pub mask: Modifiers, pub keysym: ModifiedKeySym, pub action: Action, pub latch: Option, } #[derive(Debug, Clone)] pub struct NamedAction { pub name: Rc, pub action: Action, } #[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 auto_reload: Option, pub log_level: Option, pub clean_logs_older_than: 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 grace_period: Option, pub explicit_sync_enabled: Option, pub focus_follows_mouse: bool, pub window_management_key: Option, pub vrr: Option, pub tearing: Option, pub libei: Libei, pub ui_drag: UiDrag, pub animations: Animations, pub xwayland: Option, pub color_management: Option, pub float: Option, pub named_actions: Vec, pub max_action_depth: u64, pub client_rules: Vec, pub window_rules: Vec, pub pointer_revert_key: Option, pub use_hardware_cursor: Option, pub show_bar: Option, pub show_titles: Option, pub focus_history: Option, pub middle_click_paste: Option, pub input_modes: AHashMap, pub workspace_display_order: Option, pub simple_im: Option, pub fallback_output_mode: Option, pub mouse_follows_focus: Option, pub scratchpads: Vec, pub autotile: Option, } #[derive(Debug, Clone)] pub struct Scratchpad { pub name: String, pub exec: 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], mark_names: &RefCell>, handle_error: F, ) -> Option 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")); }