From 64c41fa2c8853aefc8f62bf9492043a6c25b8c8f Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sun, 7 Jan 2024 09:07:22 +0400 Subject: Move config into a separate crate Get miette and knuffel deps contained within. --- Cargo.lock | 15 +- Cargo.toml | 42 ++- niri-config/Cargo.toml | 16 + niri-config/src/lib.rs | 889 +++++++++++++++++++++++++++++++++++++++++++++++ src/backend/tty.rs | 2 +- src/backend/winit.rs | 2 +- src/config.rs | 878 ---------------------------------------------- src/input.rs | 5 +- src/layout/focus_ring.rs | 7 +- src/layout/mod.rs | 8 +- src/layout/monitor.rs | 2 +- src/layout/workspace.rs | 2 +- src/main.rs | 10 +- src/niri.rs | 2 +- src/screenshot_ui.rs | 2 +- src/utils.rs | 3 +- 16 files changed, 968 insertions(+), 917 deletions(-) create mode 100644 niri-config/Cargo.toml create mode 100644 niri-config/src/lib.rs delete mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 96be62b0..56007a49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1704,11 +1704,10 @@ dependencies = [ "directories", "git-version", "keyframe", - "knuffel", "libc", "log", "logind-zbus", - "miette", + "niri-config", "notify-rust", "pipewire", "png", @@ -1728,6 +1727,18 @@ dependencies = [ "zbus", ] +[[package]] +name = "niri-config" +version = "0.1.0-alpha.3" +dependencies = [ + "bitflags 2.4.1", + "directories", + "knuffel", + "miette", + "smithay", + "tracing", +] + [[package]] name = "nix" version = "0.26.4" diff --git a/Cargo.toml b/Cargo.toml index e5807286..b8b6d6fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,35 @@ -[package] -name = "niri" +[workspace.package] version = "0.1.0-alpha.3" description = "A scrollable-tiling Wayland compositor" authors = ["Ivan Molodetskikh "] license = "GPL-3.0-or-later" edition = "2021" +repository = "https://github.com/YaLTeR/niri" + +[workspace.dependencies] +bitflags = "2.4.1" +directories = "5.0.1" +tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] } + +[workspace.dependencies.smithay] +git = "https://github.com/Smithay/smithay.git" +# path = "../smithay" +default-features = false + +[workspace.dependencies.smithay-drm-extras] +git = "https://github.com/Smithay/smithay.git" +# path = "../smithay/smithay-drm-extras" + +[package] +name = "niri" +version.workspace = true +description.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true readme = "README.md" -repository = "https://github.com/YaLTeR/niri" keywords = ["wayland", "compositor", "tiling", "smithay", "wm"] [dependencies] @@ -20,29 +42,27 @@ clap = { version = "4.4.13", features = ["derive"] } directories = "5.0.1" git-version = "0.3.9" keyframe = { version = "1.1.1", default-features = false } -knuffel = "3.2.0" libc = "0.2.151" logind-zbus = { version = "3.1.2", optional = true } log = { version = "0.4.20", features = ["max_level_trace", "release_max_level_debug"] } -miette = "5.10.0" +niri-config = { version = "0.1.0-alpha.3", path = "niri-config" } notify-rust = { version = "4.10.0", optional = true } pipewire = { version = "0.7.2", optional = true } png = "0.17.10" portable-atomic = { version = "1.6.0", default-features = false, features = ["float"] } profiling = "1.0.13" sd-notify = "0.4.1" +smithay-drm-extras.workspace = true serde = { version = "1.0.195", features = ["derive"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] } +tracing.workspace = true tracy-client = { version = "0.16.5", default-features = false } url = { version = "2.5.0", optional = true } xcursor = "0.3.5" zbus = { version = "3.14.1", optional = true } [dependencies.smithay] -git = "https://github.com/Smithay/smithay.git" -# path = "../smithay" -default-features = false +workspace = true features = [ "backend_drm", "backend_egl", @@ -58,10 +78,6 @@ features = [ "wayland_frontend", ] -[dependencies.smithay-drm-extras] -git = "https://github.com/Smithay/smithay.git" -# path = "../smithay/smithay-drm-extras" - [dev-dependencies] proptest = "1.4.0" proptest-derive = "0.4.0" diff --git a/niri-config/Cargo.toml b/niri-config/Cargo.toml new file mode 100644 index 00000000..a9b69e80 --- /dev/null +++ b/niri-config/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "niri-config" +version.workspace = true +description.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[dependencies] +bitflags.workspace = true +directories.workspace = true +knuffel = "3.2.0" +miette = "5.10.0" +smithay.workspace = true +tracing.workspace = true diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs new file mode 100644 index 00000000..19f6d036 --- /dev/null +++ b/niri-config/src/lib.rs @@ -0,0 +1,889 @@ +#[macro_use] +extern crate tracing; + +use std::path::PathBuf; +use std::str::FromStr; + +use bitflags::bitflags; +use directories::ProjectDirs; +use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler}; +use smithay::input::keyboard::keysyms::KEY_NoSymbol; +use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE}; +use smithay::input::keyboard::{Keysym, XkbConfig}; + +#[derive(knuffel::Decode, Debug, PartialEq)] +pub struct Config { + #[knuffel(child, default)] + pub input: Input, + #[knuffel(children(name = "output"))] + pub outputs: Vec, + #[knuffel(children(name = "spawn-at-startup"))] + pub spawn_at_startup: Vec, + #[knuffel(child, default)] + pub layout: Layout, + #[knuffel(child, default)] + pub prefer_no_csd: bool, + #[knuffel(child, default)] + pub cursor: Cursor, + #[knuffel( + child, + unwrap(argument), + default = Some(String::from( + "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" + ))) + ] + pub screenshot_path: Option, + #[knuffel(child, default)] + pub binds: Binds, + #[knuffel(child, default)] + pub debug: DebugConfig, +} + +// FIXME: Add other devices. +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Input { + #[knuffel(child, default)] + pub keyboard: Keyboard, + #[knuffel(child, default)] + pub touchpad: Touchpad, + #[knuffel(child, default)] + pub tablet: Tablet, + #[knuffel(child)] + pub disable_power_key_handling: bool, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)] +pub struct Keyboard { + #[knuffel(child, default)] + pub xkb: Xkb, + // The defaults were chosen to match wlroots and sway. + #[knuffel(child, unwrap(argument), default = 600)] + pub repeat_delay: u16, + #[knuffel(child, unwrap(argument), default = 25)] + pub repeat_rate: u8, + #[knuffel(child, unwrap(argument), default)] + pub track_layout: TrackLayout, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)] +pub struct Xkb { + #[knuffel(child, unwrap(argument), default)] + pub rules: String, + #[knuffel(child, unwrap(argument), default)] + pub model: String, + #[knuffel(child, unwrap(argument))] + pub layout: Option, + #[knuffel(child, unwrap(argument), default)] + pub variant: String, + #[knuffel(child, unwrap(argument))] + pub options: Option, +} + +impl Xkb { + pub fn to_xkb_config(&self) -> XkbConfig { + XkbConfig { + rules: &self.rules, + model: &self.model, + layout: self.layout.as_deref().unwrap_or("us"), + variant: &self.variant, + options: self.options.clone(), + } + } +} + +#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)] +pub enum TrackLayout { + /// The layout change is global. + #[default] + Global, + /// The layout change is window local. + Window, +} + +// FIXME: Add the rest of the settings. +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Touchpad { + #[knuffel(child)] + pub tap: bool, + #[knuffel(child)] + pub natural_scroll: bool, + #[knuffel(child, unwrap(argument), default)] + pub accel_speed: f64, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Tablet { + #[knuffel(child, unwrap(argument))] + pub map_to_output: Option, +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] +pub struct Output { + #[knuffel(child)] + pub off: bool, + #[knuffel(argument)] + pub name: String, + #[knuffel(child, unwrap(argument), default = 1.)] + pub scale: f64, + #[knuffel(child)] + pub position: Option, + #[knuffel(child, unwrap(argument, str))] + pub mode: Option, +} + +impl Default for Output { + fn default() -> Self { + Self { + off: false, + name: String::new(), + scale: 1., + position: None, + mode: None, + } + } +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] +pub struct Position { + #[knuffel(property)] + pub x: i32, + #[knuffel(property)] + pub y: i32, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Mode { + pub width: u16, + pub height: u16, + pub refresh: Option, +} + +#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] +pub struct Layout { + #[knuffel(child, default)] + pub focus_ring: FocusRing, + #[knuffel(child, default = default_border())] + pub border: FocusRing, + #[knuffel(child, unwrap(children), default)] + pub preset_column_widths: Vec, + #[knuffel(child)] + pub default_column_width: Option, + #[knuffel(child, unwrap(argument), default = 16)] + pub gaps: u16, + #[knuffel(child, default)] + pub struts: Struts, +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] +pub struct SpawnAtStartup { + #[knuffel(arguments)] + pub command: Vec, +} + +#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] +pub struct FocusRing { + #[knuffel(child)] + pub off: bool, + #[knuffel(child, unwrap(argument), default = 4)] + pub width: u16, + #[knuffel(child, default = Color::new(127, 200, 255, 255))] + pub active_color: Color, + #[knuffel(child, default = Color::new(80, 80, 80, 255))] + pub inactive_color: Color, +} + +impl Default for FocusRing { + fn default() -> Self { + Self { + off: false, + width: 4, + active_color: Color::new(127, 200, 255, 255), + inactive_color: Color::new(80, 80, 80, 255), + } + } +} + +pub const fn default_border() -> FocusRing { + FocusRing { + off: true, + width: 4, + active_color: Color::new(255, 200, 127, 255), + inactive_color: Color::new(80, 80, 80, 255), + } +} + +#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Color { + #[knuffel(argument)] + pub r: u8, + #[knuffel(argument)] + pub g: u8, + #[knuffel(argument)] + pub b: u8, + #[knuffel(argument)] + pub a: u8, +} + +impl Color { + pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } +} + +impl From for [f32; 4] { + fn from(c: Color) -> Self { + [c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.) + } +} + +#[derive(knuffel::Decode, Debug, PartialEq)] +pub struct Cursor { + #[knuffel(child, unwrap(argument), default = String::from("default"))] + pub xcursor_theme: String, + #[knuffel(child, unwrap(argument), default = 24)] + pub xcursor_size: u8, +} + +impl Default for Cursor { + fn default() -> Self { + Self { + xcursor_theme: String::from("default"), + xcursor_size: 24, + } + } +} + +#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] +pub enum PresetWidth { + Proportion(#[knuffel(argument)] f64), + Fixed(#[knuffel(argument)] i32), +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] +pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec); + +#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Struts { + #[knuffel(child, unwrap(argument), default)] + pub left: u16, + #[knuffel(child, unwrap(argument), default)] + pub right: u16, + #[knuffel(child, unwrap(argument), default)] + pub top: u16, + #[knuffel(child, unwrap(argument), default)] + pub bottom: u16, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Binds(#[knuffel(children)] pub Vec); + +#[derive(knuffel::Decode, Debug, PartialEq)] +pub struct Bind { + #[knuffel(node_name)] + pub key: Key, + #[knuffel(children)] + pub actions: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Key { + pub keysym: Keysym, + pub modifiers: Modifiers, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct Modifiers : u8 { + const CTRL = 1; + const SHIFT = 2; + const ALT = 4; + const SUPER = 8; + const COMPOSITOR = 16; + } +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] +pub enum Action { + Quit, + #[knuffel(skip)] + ChangeVt(i32), + Suspend, + PowerOffMonitors, + ToggleDebugTint, + Spawn(#[knuffel(arguments)] Vec), + #[knuffel(skip)] + ConfirmScreenshot, + #[knuffel(skip)] + CancelScreenshot, + Screenshot, + ScreenshotScreen, + ScreenshotWindow, + CloseWindow, + FullscreenWindow, + FocusColumnLeft, + FocusColumnRight, + FocusColumnFirst, + FocusColumnLast, + FocusWindowDown, + FocusWindowUp, + FocusWindowOrWorkspaceDown, + FocusWindowOrWorkspaceUp, + MoveColumnLeft, + MoveColumnRight, + MoveColumnToFirst, + MoveColumnToLast, + MoveWindowDown, + MoveWindowUp, + MoveWindowDownOrToWorkspaceDown, + MoveWindowUpOrToWorkspaceUp, + ConsumeWindowIntoColumn, + ExpelWindowFromColumn, + CenterColumn, + FocusWorkspaceDown, + FocusWorkspaceUp, + FocusWorkspace(#[knuffel(argument)] u8), + MoveWindowToWorkspaceDown, + MoveWindowToWorkspaceUp, + MoveWindowToWorkspace(#[knuffel(argument)] u8), + MoveWorkspaceDown, + MoveWorkspaceUp, + FocusMonitorLeft, + FocusMonitorRight, + FocusMonitorDown, + FocusMonitorUp, + MoveWindowToMonitorLeft, + MoveWindowToMonitorRight, + MoveWindowToMonitorDown, + MoveWindowToMonitorUp, + SetWindowHeight(#[knuffel(argument, str)] SizeChange), + SwitchPresetColumnWidth, + MaximizeColumn, + SetColumnWidth(#[knuffel(argument, str)] SizeChange), + SwitchLayout(#[knuffel(argument)] LayoutAction), +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SizeChange { + SetFixed(i32), + SetProportion(f64), + AdjustFixed(i32), + AdjustProportion(f64), +} + +#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)] +pub enum LayoutAction { + Next, + Prev, +} + +#[derive(knuffel::Decode, Debug, PartialEq)] +pub struct DebugConfig { + #[knuffel(child, unwrap(argument), default = 1.)] + pub animation_slowdown: f64, + #[knuffel(child)] + pub dbus_interfaces_in_non_session_instances: bool, + #[knuffel(child)] + pub wait_for_frame_completion_before_queueing: bool, + #[knuffel(child)] + pub enable_color_transformations_capability: bool, + #[knuffel(child)] + pub enable_overlay_planes: bool, + #[knuffel(child)] + pub disable_cursor_plane: bool, + #[knuffel(child, unwrap(argument))] + pub render_drm_device: Option, +} + +impl Default for DebugConfig { + fn default() -> Self { + Self { + animation_slowdown: 1., + dbus_interfaces_in_non_session_instances: false, + wait_for_frame_completion_before_queueing: false, + enable_color_transformations_capability: false, + enable_overlay_planes: false, + disable_cursor_plane: false, + render_drm_device: None, + } + } +} + +impl Config { + pub fn load(path: Option) -> miette::Result<(Self, PathBuf)> { + Self::load_internal(path).context("error loading config") + } + + fn load_internal(path: Option) -> miette::Result<(Self, PathBuf)> { + let path = if let Some(path) = path { + path + } else { + let mut path = ProjectDirs::from("", "", "niri") + .ok_or_else(|| miette!("error retrieving home directory"))? + .config_dir() + .to_owned(); + path.push("config.kdl"); + path + }; + + let contents = std::fs::read_to_string(&path) + .into_diagnostic() + .with_context(|| format!("error reading {path:?}"))?; + + let config = Self::parse("config.kdl", &contents).context("error parsing")?; + debug!("loaded config from {path:?}"); + Ok((config, path)) + } + + pub fn parse(filename: &str, text: &str) -> Result { + knuffel::parse(filename, text) + } +} + +impl Default for Config { + fn default() -> Self { + Config::parse( + "default-config.kdl", + include_str!("../../resources/default-config.kdl"), + ) + .unwrap() + } +} + +impl FromStr for Mode { + type Err = miette::Error; + + fn from_str(s: &str) -> Result { + let Some((width, rest)) = s.split_once('x') else { + return Err(miette!("no 'x' separator found")); + }; + + let (height, refresh) = match rest.split_once('@') { + Some((height, refresh)) => (height, Some(refresh)), + None => (rest, None), + }; + + let width = width + .parse() + .into_diagnostic() + .context("error parsing width")?; + let height = height + .parse() + .into_diagnostic() + .context("error parsing height")?; + let refresh = refresh + .map(str::parse) + .transpose() + .into_diagnostic() + .context("error parsing refresh rate")?; + + Ok(Self { + width, + height, + refresh, + }) + } +} + +impl FromStr for Key { + type Err = miette::Error; + + fn from_str(s: &str) -> Result { + let mut modifiers = Modifiers::empty(); + + let mut split = s.split('+'); + let key = split.next_back().unwrap(); + + for part in split { + let part = part.trim(); + if part.eq_ignore_ascii_case("mod") { + modifiers |= Modifiers::COMPOSITOR + } else if part.eq_ignore_ascii_case("ctrl") || part.eq_ignore_ascii_case("control") { + modifiers |= Modifiers::CTRL; + } else if part.eq_ignore_ascii_case("shift") { + modifiers |= Modifiers::SHIFT; + } else if part.eq_ignore_ascii_case("alt") { + modifiers |= Modifiers::ALT; + } else if part.eq_ignore_ascii_case("super") || part.eq_ignore_ascii_case("win") { + modifiers |= Modifiers::SUPER; + } else { + return Err(miette!("invalid modifier: {part}")); + } + } + + let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE); + if keysym.raw() == KEY_NoSymbol { + return Err(miette!("invalid key: {key}")); + } + + Ok(Key { keysym, modifiers }) + } +} + +impl FromStr for SizeChange { + type Err = miette::Error; + + fn from_str(s: &str) -> Result { + match s.split_once('%') { + Some((value, empty)) => { + if !empty.is_empty() { + return Err(miette!("trailing characters after '%' are not allowed")); + } + + match value.bytes().next() { + Some(b'-' | b'+') => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::AdjustProportion(value)) + } + Some(_) => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::SetProportion(value)) + } + None => Err(miette!("value is missing")), + } + } + None => { + let value = s; + match value.bytes().next() { + Some(b'-' | b'+') => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::AdjustFixed(value)) + } + Some(_) => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::SetFixed(value)) + } + None => Err(miette!("value is missing")), + } + } + } + } +} + +pub fn set_miette_hook() -> Result<(), miette::InstallError> { + miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new()))) +} + +#[cfg(test)] +mod tests { + use miette::NarratableReportHandler; + + use super::*; + + #[track_caller] + fn check(text: &str, expected: Config) { + let _ = miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new()))); + + let parsed = Config::parse("test.kdl", text) + .map_err(miette::Report::new) + .unwrap(); + assert_eq!(parsed, expected); + } + + #[test] + fn parse() { + check( + r#" + input { + keyboard { + repeat-delay 600 + repeat-rate 25 + track-layout "window" + xkb { + layout "us,ru" + options "grp:win_space_toggle" + } + } + + touchpad { + tap + accel-speed 0.2 + } + + tablet { + map-to-output "eDP-1" + } + + disable-power-key-handling + } + + output "eDP-1" { + scale 2.0 + position x=10 y=20 + mode "1920x1080@144" + } + + layout { + focus-ring { + width 5 + active-color 0 100 200 255 + inactive-color 255 200 100 0 + } + + border { + width 3 + active-color 0 100 200 255 + inactive-color 255 200 100 0 + } + + preset-column-widths { + proportion 0.25 + proportion 0.5 + fixed 960 + fixed 1280 + } + + default-column-width { proportion 0.25; } + + gaps 8 + + struts { + left 1 + right 2 + top 3 + } + } + + spawn-at-startup "alacritty" "-e" "fish" + + prefer-no-csd + + cursor { + xcursor-theme "breeze_cursors" + xcursor-size 16 + } + + screenshot-path "~/Screenshots/screenshot.png" + + binds { + Mod+T { spawn "alacritty"; } + Mod+Q { close-window; } + Mod+Shift+H { focus-monitor-left; } + Mod+Ctrl+Shift+L { move-window-to-monitor-right; } + Mod+Comma { consume-window-into-column; } + Mod+1 { focus-workspace 1;} + } + + debug { + animation-slowdown 2.0 + render-drm-device "/dev/dri/renderD129" + } + "#, + Config { + input: Input { + keyboard: Keyboard { + xkb: Xkb { + layout: Some("us,ru".to_owned()), + options: Some("grp:win_space_toggle".to_owned()), + ..Default::default() + }, + repeat_delay: 600, + repeat_rate: 25, + track_layout: TrackLayout::Window, + }, + touchpad: Touchpad { + tap: true, + natural_scroll: false, + accel_speed: 0.2, + }, + tablet: Tablet { + map_to_output: Some("eDP-1".to_owned()), + }, + disable_power_key_handling: true, + }, + outputs: vec![Output { + off: false, + name: "eDP-1".to_owned(), + scale: 2., + position: Some(Position { x: 10, y: 20 }), + mode: Some(Mode { + width: 1920, + height: 1080, + refresh: Some(144.), + }), + }], + layout: Layout { + focus_ring: FocusRing { + off: false, + width: 5, + active_color: Color { + r: 0, + g: 100, + b: 200, + a: 255, + }, + inactive_color: Color { + r: 255, + g: 200, + b: 100, + a: 0, + }, + }, + border: FocusRing { + off: false, + width: 3, + active_color: Color { + r: 0, + g: 100, + b: 200, + a: 255, + }, + inactive_color: Color { + r: 255, + g: 200, + b: 100, + a: 0, + }, + }, + preset_column_widths: vec![ + PresetWidth::Proportion(0.25), + PresetWidth::Proportion(0.5), + PresetWidth::Fixed(960), + PresetWidth::Fixed(1280), + ], + default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion( + 0.25, + )])), + gaps: 8, + struts: Struts { + left: 1, + right: 2, + top: 3, + bottom: 0, + }, + }, + spawn_at_startup: vec![SpawnAtStartup { + command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()], + }], + prefer_no_csd: true, + cursor: Cursor { + xcursor_theme: String::from("breeze_cursors"), + xcursor_size: 16, + }, + screenshot_path: Some(String::from("~/Screenshots/screenshot.png")), + binds: Binds(vec![ + Bind { + key: Key { + keysym: Keysym::t, + modifiers: Modifiers::COMPOSITOR, + }, + actions: vec![Action::Spawn(vec!["alacritty".to_owned()])], + }, + Bind { + key: Key { + keysym: Keysym::q, + modifiers: Modifiers::COMPOSITOR, + }, + actions: vec![Action::CloseWindow], + }, + Bind { + key: Key { + keysym: Keysym::h, + modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT, + }, + actions: vec![Action::FocusMonitorLeft], + }, + Bind { + key: Key { + keysym: Keysym::l, + modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL, + }, + actions: vec![Action::MoveWindowToMonitorRight], + }, + Bind { + key: Key { + keysym: Keysym::comma, + modifiers: Modifiers::COMPOSITOR, + }, + actions: vec![Action::ConsumeWindowIntoColumn], + }, + Bind { + key: Key { + keysym: Keysym::_1, + modifiers: Modifiers::COMPOSITOR, + }, + actions: vec![Action::FocusWorkspace(1)], + }, + ]), + debug: DebugConfig { + animation_slowdown: 2., + render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")), + ..Default::default() + }, + }, + ); + } + + #[test] + fn can_create_default_config() { + let _ = Config::default(); + } + + #[test] + fn parse_mode() { + assert_eq!( + "2560x1600@165.004".parse::().unwrap(), + Mode { + width: 2560, + height: 1600, + refresh: Some(165.004), + }, + ); + + assert_eq!( + "1920x1080".parse::().unwrap(), + Mode { + width: 1920, + height: 1080, + refresh: None, + }, + ); + + assert!("1920".parse::().is_err()); + assert!("1920x".parse::().is_err()); + assert!("1920x1080@".parse::().is_err()); + assert!("1920x1080@60Hz".parse::().is_err()); + } + + #[test] + fn parse_size_change() { + assert_eq!( + "10".parse::().unwrap(), + SizeChange::SetFixed(10), + ); + assert_eq!( + "+10".parse::().unwrap(), + SizeChange::AdjustFixed(10), + ); + assert_eq!( + "-10".parse::().unwrap(), + SizeChange::AdjustFixed(-10), + ); + assert_eq!( + "10%".parse::().unwrap(), + SizeChange::SetProportion(10.), + ); + assert_eq!( + "+10%".parse::().unwrap(), + SizeChange::AdjustProportion(10.), + ); + assert_eq!( + "-10%".parse::().unwrap(), + SizeChange::AdjustProportion(-10.), + ); + + assert!("-".parse::().is_err()); + assert!("10% ".parse::().is_err()); + } +} diff --git a/src/backend/tty.rs b/src/backend/tty.rs index ab7a7e84..4d2afef7 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -9,6 +9,7 @@ use std::{io, mem}; use anyhow::{anyhow, Context}; use libc::dev_t; +use niri_config::Config; use smithay::backend::allocator::dmabuf::{Dmabuf, DmabufAllocator}; use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; use smithay::backend::allocator::{Format, Fourcc}; @@ -46,7 +47,6 @@ use wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_ use wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; use super::RenderResult; -use crate::config::Config; use crate::niri::{RedrawState, State}; use crate::render_helpers::AsGlesRenderer; use crate::utils::get_monotonic_time; diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 492215b5..af09a487 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::Duration; +use niri_config::Config; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::gles::GlesRenderer; @@ -18,7 +19,6 @@ use smithay::reexports::winit::window::WindowBuilder; use smithay::utils::Transform; use super::RenderResult; -use crate::config::Config; use crate::niri::{RedrawState, State}; use crate::utils::get_monotonic_time; use crate::Niri; diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index b1815459..00000000 --- a/src/config.rs +++ /dev/null @@ -1,878 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use bitflags::bitflags; -use directories::ProjectDirs; -use miette::{miette, Context, IntoDiagnostic}; -use smithay::input::keyboard::keysyms::KEY_NoSymbol; -use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE}; -use smithay::input::keyboard::{Keysym, XkbConfig}; - -#[derive(knuffel::Decode, Debug, PartialEq)] -pub struct Config { - #[knuffel(child, default)] - pub input: Input, - #[knuffel(children(name = "output"))] - pub outputs: Vec, - #[knuffel(children(name = "spawn-at-startup"))] - pub spawn_at_startup: Vec, - #[knuffel(child, default)] - pub layout: Layout, - #[knuffel(child, default)] - pub prefer_no_csd: bool, - #[knuffel(child, default)] - pub cursor: Cursor, - #[knuffel( - child, - unwrap(argument), - default = Some(String::from( - "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" - ))) - ] - pub screenshot_path: Option, - #[knuffel(child, default)] - pub binds: Binds, - #[knuffel(child, default)] - pub debug: DebugConfig, -} - -// FIXME: Add other devices. -#[derive(knuffel::Decode, Debug, Default, PartialEq)] -pub struct Input { - #[knuffel(child, default)] - pub keyboard: Keyboard, - #[knuffel(child, default)] - pub touchpad: Touchpad, - #[knuffel(child, default)] - pub tablet: Tablet, - #[knuffel(child)] - pub disable_power_key_handling: bool, -} - -#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)] -pub struct Keyboard { - #[knuffel(child, default)] - pub xkb: Xkb, - // The defaults were chosen to match wlroots and sway. - #[knuffel(child, unwrap(argument), default = 600)] - pub repeat_delay: u16, - #[knuffel(child, unwrap(argument), default = 25)] - pub repeat_rate: u8, - #[knuffel(child, unwrap(argument), default)] - pub track_layout: TrackLayout, -} - -#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)] -pub struct Xkb { - #[knuffel(child, unwrap(argument), default)] - pub rules: String, - #[knuffel(child, unwrap(argument), default)] - pub model: String, - #[knuffel(child, unwrap(argument))] - pub layout: Option, - #[knuffel(child, unwrap(argument), default)] - pub variant: String, - #[knuffel(child, unwrap(argument))] - pub options: Option, -} - -impl Xkb { - pub fn to_xkb_config(&self) -> XkbConfig { - XkbConfig { - rules: &self.rules, - model: &self.model, - layout: self.layout.as_deref().unwrap_or("us"), - variant: &self.variant, - options: self.options.clone(), - } - } -} - -#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)] -pub enum TrackLayout { - /// The layout change is global. - #[default] - Global, - /// The layout change is window local. - Window, -} - -// FIXME: Add the rest of the settings. -#[derive(knuffel::Decode, Debug, Default, PartialEq)] -pub struct Touchpad { - #[knuffel(child)] - pub tap: bool, - #[knuffel(child)] - pub natural_scroll: bool, - #[knuffel(child, unwrap(argument), default)] - pub accel_speed: f64, -} - -#[derive(knuffel::Decode, Debug, Default, PartialEq)] -pub struct Tablet { - #[knuffel(child, unwrap(argument))] - pub map_to_output: Option, -} - -#[derive(knuffel::Decode, Debug, Clone, PartialEq)] -pub struct Output { - #[knuffel(child)] - pub off: bool, - #[knuffel(argument)] - pub name: String, - #[knuffel(child, unwrap(argument), default = 1.)] - pub scale: f64, - #[knuffel(child)] - pub position: Option, - #[knuffel(child, unwrap(argument, str))] - pub mode: Option, -} - -impl Default for Output { - fn default() -> Self { - Self { - off: false, - name: String::new(), - scale: 1., - position: None, - mode: None, - } - } -} - -#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] -pub struct Position { - #[knuffel(property)] - pub x: i32, - #[knuffel(property)] - pub y: i32, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Mode { - pub width: u16, - pub height: u16, - pub refresh: Option, -} - -#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] -pub struct Layout { - #[knuffel(child, default)] - pub focus_ring: FocusRing, - #[knuffel(child, default = default_border())] - pub border: FocusRing, - #[knuffel(child, unwrap(children), default)] - pub preset_column_widths: Vec, - #[knuffel(child)] - pub default_column_width: Option, - #[knuffel(child, unwrap(argument), default = 16)] - pub gaps: u16, - #[knuffel(child, default)] - pub struts: Struts, -} - -#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] -pub struct SpawnAtStartup { - #[knuffel(arguments)] - pub command: Vec, -} - -#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] -pub struct FocusRing { - #[knuffel(child)] - pub off: bool, - #[knuffel(child, unwrap(argument), default = 4)] - pub width: u16, - #[knuffel(child, default = Color::new(127, 200, 255, 255))] - pub active_color: Color, - #[knuffel(child, default = Color::new(80, 80, 80, 255))] - pub inactive_color: Color, -} - -impl Default for FocusRing { - fn default() -> Self { - Self { - off: false, - width: 4, - active_color: Color::new(127, 200, 255, 255), - inactive_color: Color::new(80, 80, 80, 255), - } - } -} - -pub const fn default_border() -> FocusRing { - FocusRing { - off: true, - width: 4, - active_color: Color::new(255, 200, 127, 255), - inactive_color: Color::new(80, 80, 80, 255), - } -} - -#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] -pub struct Color { - #[knuffel(argument)] - pub r: u8, - #[knuffel(argument)] - pub g: u8, - #[knuffel(argument)] - pub b: u8, - #[knuffel(argument)] - pub a: u8, -} - -impl Color { - pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } -} - -impl From for [f32; 4] { - fn from(c: Color) -> Self { - [c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.) - } -} - -#[derive(knuffel::Decode, Debug, PartialEq)] -pub struct Cursor { - #[knuffel(child, unwrap(argument), default = String::from("default"))] - pub xcursor_theme: String, - #[knuffel(child, unwrap(argument), default = 24)] - pub xcursor_size: u8, -} - -impl Default for Cursor { - fn default() -> Self { - Self { - xcursor_theme: String::from("default"), - xcursor_size: 24, - } - } -} - -#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] -pub enum PresetWidth { - Proportion(#[knuffel(argument)] f64), - Fixed(#[knuffel(argument)] i32), -} - -#[derive(knuffel::Decode, Debug, Clone, PartialEq)] -pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec); - -#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] -pub struct Struts { - #[knuffel(child, unwrap(argument), default)] - pub left: u16, - #[knuffel(child, unwrap(argument), default)] - pub right: u16, - #[knuffel(child, unwrap(argument), default)] - pub top: u16, - #[knuffel(child, unwrap(argument), default)] - pub bottom: u16, -} - -#[derive(knuffel::Decode, Debug, Default, PartialEq)] -pub struct Binds(#[knuffel(children)] pub Vec); - -#[derive(knuffel::Decode, Debug, PartialEq)] -pub struct Bind { - #[knuffel(node_name)] - pub key: Key, - #[knuffel(children)] - pub actions: Vec, -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Key { - pub keysym: Keysym, - pub modifiers: Modifiers, -} - -bitflags! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub struct Modifiers : u8 { - const CTRL = 1; - const SHIFT = 2; - const ALT = 4; - const SUPER = 8; - const COMPOSITOR = 16; - } -} - -#[derive(knuffel::Decode, Debug, Clone, PartialEq)] -pub enum Action { - Quit, - #[knuffel(skip)] - ChangeVt(i32), - Suspend, - PowerOffMonitors, - ToggleDebugTint, - Spawn(#[knuffel(arguments)] Vec), - #[knuffel(skip)] - ConfirmScreenshot, - #[knuffel(skip)] - CancelScreenshot, - Screenshot, - ScreenshotScreen, - ScreenshotWindow, - CloseWindow, - FullscreenWindow, - FocusColumnLeft, - FocusColumnRight, - FocusColumnFirst, - FocusColumnLast, - FocusWindowDown, - FocusWindowUp, - FocusWindowOrWorkspaceDown, - FocusWindowOrWorkspaceUp, - MoveColumnLeft, - MoveColumnRight, - MoveColumnToFirst, - MoveColumnToLast, - MoveWindowDown, - MoveWindowUp, - MoveWindowDownOrToWorkspaceDown, - MoveWindowUpOrToWorkspaceUp, - ConsumeWindowIntoColumn, - ExpelWindowFromColumn, - CenterColumn, - FocusWorkspaceDown, - FocusWorkspaceUp, - FocusWorkspace(#[knuffel(argument)] u8), - MoveWindowToWorkspaceDown, - MoveWindowToWorkspaceUp, - MoveWindowToWorkspace(#[knuffel(argument)] u8), - MoveWorkspaceDown, - MoveWorkspaceUp, - FocusMonitorLeft, - FocusMonitorRight, - FocusMonitorDown, - FocusMonitorUp, - MoveWindowToMonitorLeft, - MoveWindowToMonitorRight, - MoveWindowToMonitorDown, - MoveWindowToMonitorUp, - SetWindowHeight(#[knuffel(argument, str)] SizeChange), - SwitchPresetColumnWidth, - MaximizeColumn, - SetColumnWidth(#[knuffel(argument, str)] SizeChange), - SwitchLayout(#[knuffel(argument)] LayoutAction), -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SizeChange { - SetFixed(i32), - SetProportion(f64), - AdjustFixed(i32), - AdjustProportion(f64), -} - -#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)] -pub enum LayoutAction { - Next, - Prev, -} - -#[derive(knuffel::Decode, Debug, PartialEq)] -pub struct DebugConfig { - #[knuffel(child, unwrap(argument), default = 1.)] - pub animation_slowdown: f64, - #[knuffel(child)] - pub dbus_interfaces_in_non_session_instances: bool, - #[knuffel(child)] - pub wait_for_frame_completion_before_queueing: bool, - #[knuffel(child)] - pub enable_color_transformations_capability: bool, - #[knuffel(child)] - pub enable_overlay_planes: bool, - #[knuffel(child)] - pub disable_cursor_plane: bool, - #[knuffel(child, unwrap(argument))] - pub render_drm_device: Option, -} - -impl Default for DebugConfig { - fn default() -> Self { - Self { - animation_slowdown: 1., - dbus_interfaces_in_non_session_instances: false, - wait_for_frame_completion_before_queueing: false, - enable_color_transformations_capability: false, - enable_overlay_planes: false, - disable_cursor_plane: false, - render_drm_device: None, - } - } -} - -impl Config { - pub fn load(path: Option) -> miette::Result<(Self, PathBuf)> { - let path = if let Some(path) = path { - path - } else { - let mut path = ProjectDirs::from("", "", "niri") - .ok_or_else(|| miette!("error retrieving home directory"))? - .config_dir() - .to_owned(); - path.push("config.kdl"); - path - }; - - let contents = std::fs::read_to_string(&path) - .into_diagnostic() - .with_context(|| format!("error reading {path:?}"))?; - - let config = Self::parse("config.kdl", &contents).context("error parsing")?; - debug!("loaded config from {path:?}"); - Ok((config, path)) - } - - pub fn parse(filename: &str, text: &str) -> Result { - knuffel::parse(filename, text) - } -} - -impl Default for Config { - fn default() -> Self { - Config::parse( - "default-config.kdl", - include_str!("../resources/default-config.kdl"), - ) - .unwrap() - } -} - -impl FromStr for Mode { - type Err = miette::Error; - - fn from_str(s: &str) -> Result { - let Some((width, rest)) = s.split_once('x') else { - return Err(miette!("no 'x' separator found")); - }; - - let (height, refresh) = match rest.split_once('@') { - Some((height, refresh)) => (height, Some(refresh)), - None => (rest, None), - }; - - let width = width - .parse() - .into_diagnostic() - .context("error parsing width")?; - let height = height - .parse() - .into_diagnostic() - .context("error parsing height")?; - let refresh = refresh - .map(str::parse) - .transpose() - .into_diagnostic() - .context("error parsing refresh rate")?; - - Ok(Self { - width, - height, - refresh, - }) - } -} - -impl FromStr for Key { - type Err = miette::Error; - - fn from_str(s: &str) -> Result { - let mut modifiers = Modifiers::empty(); - - let mut split = s.split('+'); - let key = split.next_back().unwrap(); - - for part in split { - let part = part.trim(); - if part.eq_ignore_ascii_case("mod") { - modifiers |= Modifiers::COMPOSITOR - } else if part.eq_ignore_ascii_case("ctrl") || part.eq_ignore_ascii_case("control") { - modifiers |= Modifiers::CTRL; - } else if part.eq_ignore_ascii_case("shift") { - modifiers |= Modifiers::SHIFT; - } else if part.eq_ignore_ascii_case("alt") { - modifiers |= Modifiers::ALT; - } else if part.eq_ignore_ascii_case("super") || part.eq_ignore_ascii_case("win") { - modifiers |= Modifiers::SUPER; - } else { - return Err(miette!("invalid modifier: {part}")); - } - } - - let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE); - if keysym.raw() == KEY_NoSymbol { - return Err(miette!("invalid key: {key}")); - } - - Ok(Key { keysym, modifiers }) - } -} - -impl FromStr for SizeChange { - type Err = miette::Error; - - fn from_str(s: &str) -> Result { - match s.split_once('%') { - Some((value, empty)) => { - if !empty.is_empty() { - return Err(miette!("trailing characters after '%' are not allowed")); - } - - match value.bytes().next() { - Some(b'-' | b'+') => { - let value = value - .parse() - .into_diagnostic() - .context("error parsing value")?; - Ok(Self::AdjustProportion(value)) - } - Some(_) => { - let value = value - .parse() - .into_diagnostic() - .context("error parsing value")?; - Ok(Self::SetProportion(value)) - } - None => Err(miette!("value is missing")), - } - } - None => { - let value = s; - match value.bytes().next() { - Some(b'-' | b'+') => { - let value = value - .parse() - .into_diagnostic() - .context("error parsing value")?; - Ok(Self::AdjustFixed(value)) - } - Some(_) => { - let value = value - .parse() - .into_diagnostic() - .context("error parsing value")?; - Ok(Self::SetFixed(value)) - } - None => Err(miette!("value is missing")), - } - } - } - } -} - -#[cfg(test)] -mod tests { - use miette::NarratableReportHandler; - - use super::*; - - #[track_caller] - fn check(text: &str, expected: Config) { - let _ = miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new()))); - - let parsed = Config::parse("test.kdl", text) - .map_err(miette::Report::new) - .unwrap(); - assert_eq!(parsed, expected); - } - - #[test] - fn parse() { - check( - r#" - input { - keyboard { - repeat-delay 600 - repeat-rate 25 - track-layout "window" - xkb { - layout "us,ru" - options "grp:win_space_toggle" - } - } - - touchpad { - tap - accel-speed 0.2 - } - - tablet { - map-to-output "eDP-1" - } - - disable-power-key-handling - } - - output "eDP-1" { - scale 2.0 - position x=10 y=20 - mode "1920x1080@144" - } - - layout { - focus-ring { - width 5 - active-color 0 100 200 255 - inactive-color 255 200 100 0 - } - - border { - width 3 - active-color 0 100 200 255 - inactive-color 255 200 100 0 - } - - preset-column-widths { - proportion 0.25 - proportion 0.5 - fixed 960 - fixed 1280 - } - - default-column-width { proportion 0.25; } - - gaps 8 - - struts { - left 1 - right 2 - top 3 - } - } - - spawn-at-startup "alacritty" "-e" "fish" - - prefer-no-csd - - cursor { - xcursor-theme "breeze_cursors" - xcursor-size 16 - } - - screenshot-path "~/Screenshots/screenshot.png" - - binds { - Mod+T { spawn "alacritty"; } - Mod+Q { close-window; } - Mod+Shift+H { focus-monitor-left; } - Mod+Ctrl+Shift+L { move-window-to-monitor-right; } - Mod+Comma { consume-window-into-column; } - Mod+1 { focus-workspace 1;} - } - - debug { - animation-slowdown 2.0 - render-drm-device "/dev/dri/renderD129" - } - "#, - Config { - input: Input { - keyboard: Keyboard { - xkb: Xkb { - layout: Some("us,ru".to_owned()), - options: Some("grp:win_space_toggle".to_owned()), - ..Default::default() - }, - repeat_delay: 600, - repeat_rate: 25, - track_layout: TrackLayout::Window, - }, - touchpad: Touchpad { - tap: true, - natural_scroll: false, - accel_speed: 0.2, - }, - tablet: Tablet { - map_to_output: Some("eDP-1".to_owned()), - }, - disable_power_key_handling: true, - }, - outputs: vec![Output { - off: false, - name: "eDP-1".to_owned(), - scale: 2., - position: Some(Position { x: 10, y: 20 }), - mode: Some(Mode { - width: 1920, - height: 1080, - refresh: Some(144.), - }), - }], - layout: Layout { - focus_ring: FocusRing { - off: false, - width: 5, - active_color: Color { - r: 0, - g: 100, - b: 200, - a: 255, - }, - inactive_color: Color { - r: 255, - g: 200, - b: 100, - a: 0, - }, - }, - border: FocusRing { - off: false, - width: 3, - active_color: Color { - r: 0, - g: 100, - b: 200, - a: 255, - }, - inactive_color: Color { - r: 255, - g: 200, - b: 100, - a: 0, - }, - }, - preset_column_widths: vec![ - PresetWidth::Proportion(0.25), - PresetWidth::Proportion(0.5), - PresetWidth::Fixed(960), - PresetWidth::Fixed(1280), - ], - default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion( - 0.25, - )])), - gaps: 8, - struts: Struts { - left: 1, - right: 2, - top: 3, - bottom: 0, - }, - }, - spawn_at_startup: vec![SpawnAtStartup { - command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()], - }], - prefer_no_csd: true, - cursor: Cursor { - xcursor_theme: String::from("breeze_cursors"), - xcursor_size: 16, - }, - screenshot_path: Some(String::from("~/Screenshots/screenshot.png")), - binds: Binds(vec![ - Bind { - key: Key { - keysym: Keysym::t, - modifiers: Modifiers::COMPOSITOR, - }, - actions: vec![Action::Spawn(vec!["alacritty".to_owned()])], - }, - Bind { - key: Key { - keysym: Keysym::q, - modifiers: Modifiers::COMPOSITOR, - }, - actions: vec![Action::CloseWindow], - }, - Bind { - key: Key { - keysym: Keysym::h, - modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT, - }, - actions: vec![Action::FocusMonitorLeft], - }, - Bind { - key: Key { - keysym: Keysym::l, - modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL, - }, - actions: vec![Action::MoveWindowToMonitorRight], - }, - Bind { - key: Key { - keysym: Keysym::comma, - modifiers: Modifiers::COMPOSITOR, - }, - actions: vec![Action::ConsumeWindowIntoColumn], - }, - Bind { - key: Key { - keysym: Keysym::_1, - modifiers: Modifiers::COMPOSITOR, - }, - actions: vec![Action::FocusWorkspace(1)], - }, - ]), - debug: DebugConfig { - animation_slowdown: 2., - render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")), - ..Default::default() - }, - }, - ); - } - - #[test] - fn can_create_default_config() { - let _ = Config::default(); - } - - #[test] - fn parse_mode() { - assert_eq!( - "2560x1600@165.004".parse::().unwrap(), - Mode { - width: 2560, - height: 1600, - refresh: Some(165.004), - }, - ); - - assert_eq!( - "1920x1080".parse::().unwrap(), - Mode { - width: 1920, - height: 1080, - refresh: None, - }, - ); - - assert!("1920".parse::().is_err()); - assert!("1920x".parse::().is_err()); - assert!("1920x1080@".parse::().is_err()); - assert!("1920x1080@60Hz".parse::().is_err()); - } - - #[test] - fn parse_size_change() { - assert_eq!( - "10".parse::().unwrap(), - SizeChange::SetFixed(10), - ); - assert_eq!( - "+10".parse::().unwrap(), - SizeChange::AdjustFixed(10), - ); - assert_eq!( - "-10".parse::().unwrap(), - SizeChange::AdjustFixed(-10), - ); - assert_eq!( - "10%".parse::().unwrap(), - SizeChange::SetProportion(10.), - ); - assert_eq!( - "+10%".parse::().unwrap(), - SizeChange::AdjustProportion(10.), - ); - assert_eq!( - "-10%".parse::().unwrap(), - SizeChange::AdjustProportion(-10.), - ); - - assert!("-".parse::().is_err()); - assert!("10% ".parse::().is_err()); - } -} diff --git a/src/input.rs b/src/input.rs index 4865e7e0..d0a3df7e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,6 +1,7 @@ use std::any::Any; use std::collections::HashSet; +use niri_config::{Action, Binds, LayoutAction, Modifiers}; use smithay::backend::input::{ AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event, GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, @@ -20,7 +21,6 @@ use smithay::utils::{Logical, Point, SERIAL_COUNTER}; use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint}; use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait}; -use crate::config::{Action, Binds, LayoutAction, Modifiers}; use crate::niri::State; use crate::screenshot_ui::ScreenshotUi; use crate::utils::{center, get_monotonic_time, spawn}; @@ -1338,8 +1338,9 @@ fn allowed_during_screenshot(action: &Action) -> bool { #[cfg(test)] mod tests { + use niri_config::{Action, Bind, Binds, Key, Modifiers}; + use super::*; - use crate::config::{Action, Bind, Binds, Key, Modifiers}; #[test] fn bindings_suppress_keys() { diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs index b5ead3e2..6e2730b8 100644 --- a/src/layout/focus_ring.rs +++ b/src/layout/focus_ring.rs @@ -1,12 +1,11 @@ use std::iter::zip; use arrayvec::ArrayVec; +use niri_config::{self, Color}; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::Kind; use smithay::utils::{Logical, Point, Scale, Size}; -use crate::config::{self, Color}; - #[derive(Debug)] pub struct FocusRing { buffers: [SolidColorBuffer; 4], @@ -21,7 +20,7 @@ pub struct FocusRing { pub type FocusRingRenderElement = SolidColorRenderElement; impl FocusRing { - pub fn new(config: config::FocusRing) -> Self { + pub fn new(config: niri_config::FocusRing) -> Self { Self { buffers: Default::default(), locations: Default::default(), @@ -33,7 +32,7 @@ impl FocusRing { } } - pub fn update_config(&mut self, config: config::FocusRing) { + pub fn update_config(&mut self, config: niri_config::FocusRing) { self.is_off = config.off; self.width = config.width.into(); self.active_color = config.active_color; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index c7fce722..69d78676 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -33,6 +33,7 @@ use std::mem; use std::rc::Rc; use std::time::Duration; +use niri_config::{self, Config, SizeChange, Struts}; use smithay::backend::renderer::element::AsRenderElements; use smithay::backend::renderer::{ImportAll, Renderer}; use smithay::desktop::space::SpaceElement; @@ -51,7 +52,6 @@ use self::workspace::{ compute_working_area, ColumnWidth, OutputId, Workspace, WorkspaceRenderElement, }; use crate::animation::Animation; -use crate::config::{self, Config, SizeChange, Struts}; use crate::utils::output_size; mod focus_ring; @@ -137,8 +137,8 @@ pub struct Options { gaps: i32, /// Extra padding around the working area in logical pixels. struts: Struts, - focus_ring: config::FocusRing, - border: config::FocusRing, + focus_ring: niri_config::FocusRing, + border: niri_config::FocusRing, /// Column widths that `toggle_width()` switches between. preset_widths: Vec, /// Initial width for new columns. @@ -151,7 +151,7 @@ impl Default for Options { gaps: 16, struts: Default::default(), focus_ring: Default::default(), - border: config::default_border(), + border: niri_config::default_border(), preset_widths: vec![ ColumnWidth::Proportion(1. / 3.), ColumnWidth::Proportion(0.5), diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 444316a4..a5c4d6dd 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -2,6 +2,7 @@ use std::cmp::min; use std::rc::Rc; use std::time::Duration; +use niri_config::SizeChange; use smithay::backend::renderer::element::utils::{ CropRenderElement, Relocate, RelocateRenderElement, }; @@ -15,7 +16,6 @@ use super::workspace::{ }; use super::{LayoutElement, Options}; use crate