2023-09-05 12:58:51 +04:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
|
|
use bitflags::bitflags;
|
|
|
|
|
use directories::ProjectDirs;
|
|
|
|
|
use miette::{miette, Context, IntoDiagnostic};
|
2023-09-24 11:04:30 +04:00
|
|
|
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
|
|
|
|
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
2023-12-05 08:04:46 +04:00
|
|
|
use smithay::input::keyboard::{Keysym, XkbConfig};
|
2023-09-05 12:58:51 +04:00
|
|
|
|
|
|
|
|
#[derive(knuffel::Decode, Debug, PartialEq)]
|
|
|
|
|
pub struct Config {
|
|
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub input: Input,
|
2023-09-21 13:48:32 +04:00
|
|
|
#[knuffel(children(name = "output"))]
|
|
|
|
|
pub outputs: Vec<Output>,
|
2023-09-21 19:58:03 +04:00
|
|
|
#[knuffel(children(name = "spawn-at-startup"))]
|
|
|
|
|
pub spawn_at_startup: Vec<SpawnAtStartup>,
|
2023-09-05 12:58:51 +04:00
|
|
|
#[knuffel(child, default)]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub focus_ring: FocusRing,
|
2023-12-27 21:51:42 +04:00
|
|
|
#[knuffel(child, default = default_border())]
|
|
|
|
|
pub border: FocusRing,
|
2023-09-26 13:09:33 +04:00
|
|
|
#[knuffel(child, default)]
|
2023-09-26 13:44:37 +04:00
|
|
|
pub prefer_no_csd: bool,
|
|
|
|
|
#[knuffel(child, default)]
|
2023-10-01 17:42:56 +04:00
|
|
|
pub cursor: Cursor,
|
2023-10-05 09:25:07 +04:00
|
|
|
#[knuffel(child, unwrap(children), default)]
|
|
|
|
|
pub preset_column_widths: Vec<PresetWidth>,
|
2023-11-02 21:07:29 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub default_column_width: Option<DefaultColumnWidth>,
|
2023-10-07 17:45:55 +04:00
|
|
|
#[knuffel(child, unwrap(argument), default = 16)]
|
|
|
|
|
pub gaps: u16,
|
2023-12-21 08:37:30 +04:00
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub struts: Struts,
|
2023-10-31 14:23:54 +04:00
|
|
|
#[knuffel(
|
|
|
|
|
child,
|
|
|
|
|
unwrap(argument),
|
|
|
|
|
default = Some(String::from(
|
|
|
|
|
"~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
|
|
|
|
|
)))
|
|
|
|
|
]
|
|
|
|
|
pub screenshot_path: Option<String>,
|
2023-10-01 17:42:56 +04:00
|
|
|
#[knuffel(child, default)]
|
2023-09-05 12:58:51 +04:00
|
|
|
pub binds: Binds,
|
2023-09-06 15:49:46 +04:00
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub debug: DebugConfig,
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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,
|
2023-10-03 17:02:07 +04:00
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub tablet: Tablet,
|
2023-12-28 09:36:10 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub disable_power_key_handling: bool,
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
|
|
|
|
|
pub struct Keyboard {
|
|
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub xkb: Xkb,
|
2023-09-16 20:01:52 +04:00
|
|
|
// The defaults were chosen to match wlroots and sway.
|
|
|
|
|
#[knuffel(child, unwrap(argument), default = 600)]
|
2023-09-16 12:37:23 +04:00
|
|
|
pub repeat_delay: u16,
|
|
|
|
|
#[knuffel(child, unwrap(argument), default = 25)]
|
|
|
|
|
pub repeat_rate: u8,
|
2023-11-02 00:10:22 +04:00
|
|
|
#[knuffel(child, unwrap(argument), default)]
|
|
|
|
|
pub track_layout: TrackLayout,
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-08 07:58:03 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)]
|
2023-09-05 12:58:51 +04:00
|
|
|
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<String>,
|
|
|
|
|
#[knuffel(child, unwrap(argument), default)]
|
|
|
|
|
pub variant: String,
|
|
|
|
|
#[knuffel(child, unwrap(argument))]
|
|
|
|
|
pub options: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-05 08:04:46 +04:00
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-02 00:10:22 +04:00
|
|
|
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
|
|
|
|
|
pub enum TrackLayout {
|
|
|
|
|
/// The layout change is global.
|
|
|
|
|
#[default]
|
|
|
|
|
Global,
|
|
|
|
|
/// The layout change is window local.
|
|
|
|
|
Window,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
// 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,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 17:02:07 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
|
|
|
|
pub struct Tablet {
|
|
|
|
|
#[knuffel(child, unwrap(argument))]
|
|
|
|
|
pub map_to_output: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
|
|
|
|
pub struct Output {
|
2023-12-18 10:27:41 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub off: bool,
|
2023-09-21 13:48:32 +04:00
|
|
|
#[knuffel(argument)]
|
|
|
|
|
pub name: String,
|
|
|
|
|
#[knuffel(child, unwrap(argument), default = 1.)]
|
|
|
|
|
pub scale: f64,
|
2023-09-30 11:33:02 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub position: Option<Position>,
|
2023-10-03 08:35:24 +04:00
|
|
|
#[knuffel(child, unwrap(argument, str))]
|
|
|
|
|
pub mode: Option<Mode>,
|
2023-09-21 13:48:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Output {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
2023-12-18 10:27:41 +04:00
|
|
|
off: false,
|
2023-09-21 13:48:32 +04:00
|
|
|
name: String::new(),
|
|
|
|
|
scale: 1.,
|
2023-09-30 11:33:02 +04:00
|
|
|
position: None,
|
2023-10-03 08:35:24 +04:00
|
|
|
mode: None,
|
2023-09-21 13:48:32 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-30 11:33:02 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct Position {
|
|
|
|
|
#[knuffel(property)]
|
|
|
|
|
pub x: i32,
|
|
|
|
|
#[knuffel(property)]
|
|
|
|
|
pub y: i32,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 08:35:24 +04:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
|
pub struct Mode {
|
|
|
|
|
pub width: u16,
|
|
|
|
|
pub height: u16,
|
|
|
|
|
pub refresh: Option<f64>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 19:58:03 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct SpawnAtStartup {
|
|
|
|
|
#[knuffel(arguments)]
|
|
|
|
|
pub command: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub struct FocusRing {
|
|
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub off: bool,
|
|
|
|
|
#[knuffel(child, unwrap(argument), default = 4)]
|
|
|
|
|
pub width: u16,
|
2023-10-31 09:07:11 +04:00
|
|
|
#[knuffel(child, default = Color::new(127, 200, 255, 255))]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub active_color: Color,
|
2023-10-31 09:07:11 +04:00
|
|
|
#[knuffel(child, default = Color::new(80, 80, 80, 255))]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub inactive_color: Color,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for FocusRing {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
off: false,
|
|
|
|
|
width: 4,
|
2023-10-31 09:07:11 +04:00
|
|
|
active_color: Color::new(127, 200, 255, 255),
|
|
|
|
|
inactive_color: Color::new(80, 80, 80, 255),
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-27 21:51:42 +04:00
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-31 09:07:11 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub struct Color {
|
|
|
|
|
#[knuffel(argument)]
|
2023-10-31 09:07:11 +04:00
|
|
|
pub r: u8,
|
2023-09-26 13:09:33 +04:00
|
|
|
#[knuffel(argument)]
|
2023-10-31 09:07:11 +04:00
|
|
|
pub g: u8,
|
2023-09-26 13:09:33 +04:00
|
|
|
#[knuffel(argument)]
|
2023-10-31 09:07:11 +04:00
|
|
|
pub b: u8,
|
2023-09-26 13:09:33 +04:00
|
|
|
#[knuffel(argument)]
|
2023-10-31 09:07:11 +04:00
|
|
|
pub a: u8,
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Color {
|
2023-12-27 21:51:42 +04:00
|
|
|
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
2023-09-26 13:09:33 +04:00
|
|
|
Self { r, g, b, a }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Color> for [f32; 4] {
|
|
|
|
|
fn from(c: Color) -> Self {
|
2023-10-31 09:07:11 +04:00
|
|
|
[c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.)
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 17:42:56 +04:00
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub enum PresetWidth {
|
|
|
|
|
Proportion(#[knuffel(argument)] f64),
|
|
|
|
|
Fixed(#[knuffel(argument)] i32),
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-02 21:07:29 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
|
|
|
|
pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec<PresetWidth>);
|
|
|
|
|
|
2023-12-21 08:37:30 +04:00
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
2023-09-05 12:58:51 +04:00
|
|
|
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
|
|
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, PartialEq)]
|
2023-09-05 12:58:51 +04:00
|
|
|
pub struct Bind {
|
|
|
|
|
#[knuffel(node_name)]
|
|
|
|
|
pub key: Key,
|
|
|
|
|
#[knuffel(children)]
|
|
|
|
|
pub actions: Vec<Action>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
2023-09-05 12:58:51 +04:00
|
|
|
pub enum Action {
|
|
|
|
|
Quit,
|
|
|
|
|
#[knuffel(skip)]
|
|
|
|
|
ChangeVt(i32),
|
|
|
|
|
Suspend,
|
2023-10-09 18:37:43 +04:00
|
|
|
PowerOffMonitors,
|
2023-09-05 12:58:51 +04:00
|
|
|
ToggleDebugTint,
|
|
|
|
|
Spawn(#[knuffel(arguments)] Vec<String>),
|
2023-10-30 20:29:03 +04:00
|
|
|
#[knuffel(skip)]
|
|
|
|
|
ConfirmScreenshot,
|
|
|
|
|
#[knuffel(skip)]
|
|
|
|
|
CancelScreenshot,
|
|
|
|
|
Screenshot,
|
2023-10-26 16:47:59 +04:00
|
|
|
ScreenshotScreen,
|
2023-10-10 12:42:24 +04:00
|
|
|
ScreenshotWindow,
|
2023-09-05 12:58:51 +04:00
|
|
|
CloseWindow,
|
|
|
|
|
FullscreenWindow,
|
|
|
|
|
FocusColumnLeft,
|
|
|
|
|
FocusColumnRight,
|
2023-12-29 07:51:14 +04:00
|
|
|
FocusColumnFirst,
|
|
|
|
|
FocusColumnLast,
|
2023-09-05 12:58:51 +04:00
|
|
|
FocusWindowDown,
|
|
|
|
|
FocusWindowUp,
|
2023-12-19 19:25:05 +11:00
|
|
|
FocusWindowOrWorkspaceDown,
|
|
|
|
|
FocusWindowOrWorkspaceUp,
|
2023-09-05 12:58:51 +04:00
|
|
|
MoveColumnLeft,
|
|
|
|
|
MoveColumnRight,
|
2023-12-29 08:01:02 +04:00
|
|
|
MoveColumnToFirst,
|
|
|
|
|
MoveColumnToLast,
|
2023-09-05 12:58:51 +04:00
|
|
|
MoveWindowDown,
|
|
|
|
|
MoveWindowUp,
|
2023-12-19 19:25:05 +11:00
|
|
|
MoveWindowDownOrToWorkspaceDown,
|
|
|
|
|
MoveWindowUpOrToWorkspaceUp,
|
2023-09-05 12:58:51 +04:00
|
|
|
ConsumeWindowIntoColumn,
|
|
|
|
|
ExpelWindowFromColumn,
|
2023-11-13 19:08:29 +04:00
|
|
|
CenterColumn,
|
2023-09-05 12:58:51 +04:00
|
|
|
FocusWorkspaceDown,
|
|
|
|
|
FocusWorkspaceUp,
|
2023-09-16 12:14:02 +04:00
|
|
|
FocusWorkspace(#[knuffel(argument)] u8),
|
2023-09-05 12:58:51 +04:00
|
|
|
MoveWindowToWorkspaceDown,
|
|
|
|
|
MoveWindowToWorkspaceUp,
|
2023-09-16 12:14:02 +04:00
|
|
|
MoveWindowToWorkspace(#[knuffel(argument)] u8),
|
2023-10-14 20:42:10 +04:00
|
|
|
MoveWorkspaceDown,
|
|
|
|
|
MoveWorkspaceUp,
|
2023-09-05 12:58:51 +04:00
|
|
|
FocusMonitorLeft,
|
|
|
|
|
FocusMonitorRight,
|
|
|
|
|
FocusMonitorDown,
|
|
|
|
|
FocusMonitorUp,
|
|
|
|
|
MoveWindowToMonitorLeft,
|
|
|
|
|
MoveWindowToMonitorRight,
|
|
|
|
|
MoveWindowToMonitorDown,
|
|
|
|
|
MoveWindowToMonitorUp,
|
2023-11-08 11:17:06 +04:00
|
|
|
SetWindowHeight(#[knuffel(argument, str)] SizeChange),
|
2023-09-05 12:58:51 +04:00
|
|
|
SwitchPresetColumnWidth,
|
|
|
|
|
MaximizeColumn,
|
2023-10-03 11:38:42 +04:00
|
|
|
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
|
2023-11-02 00:09:31 +04:00
|
|
|
SwitchLayout(#[knuffel(argument)] LayoutAction),
|
2023-10-03 11:38:42 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub enum SizeChange {
|
|
|
|
|
SetFixed(i32),
|
|
|
|
|
SetProportion(f64),
|
|
|
|
|
AdjustFixed(i32),
|
|
|
|
|
AdjustProportion(f64),
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
2023-11-02 00:09:31 +04:00
|
|
|
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub enum LayoutAction {
|
|
|
|
|
Next,
|
|
|
|
|
Prev,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-06 15:49:46 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, PartialEq)]
|
|
|
|
|
pub struct DebugConfig {
|
|
|
|
|
#[knuffel(child, unwrap(argument), default = 1.)]
|
|
|
|
|
pub animation_slowdown: f64,
|
2023-09-08 17:54:02 +04:00
|
|
|
#[knuffel(child)]
|
2023-09-20 09:28:23 +04:00
|
|
|
pub dbus_interfaces_in_non_session_instances: bool,
|
2023-09-14 09:33:42 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub wait_for_frame_completion_before_queueing: bool,
|
2023-09-14 22:33:49 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub enable_color_transformations_capability: bool,
|
2023-09-14 22:43:12 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub enable_overlay_planes: bool,
|
2024-01-03 08:42:04 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub disable_cursor_plane: bool,
|
2023-09-06 15:49:46 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for DebugConfig {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
animation_slowdown: 1.,
|
2023-09-20 09:28:23 +04:00
|
|
|
dbus_interfaces_in_non_session_instances: false,
|
2023-09-14 09:33:42 +04:00
|
|
|
wait_for_frame_completion_before_queueing: false,
|
2023-09-14 22:33:49 +04:00
|
|
|
enable_color_transformations_capability: false,
|
2023-09-14 22:43:12 +04:00
|
|
|
enable_overlay_planes: false,
|
2024-01-03 08:42:04 +04:00
|
|
|
disable_cursor_plane: false,
|
2023-09-06 15:49:46 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
impl Config {
|
2023-09-26 19:24:50 +04:00
|
|
|
pub fn load(path: Option<PathBuf>) -> miette::Result<(Self, PathBuf)> {
|
2023-09-05 12:58:51 +04:00
|
|
|
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:?}");
|
2023-09-26 19:24:50 +04:00
|
|
|
Ok((config, path))
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn parse(filename: &str, text: &str) -> Result<Self, knuffel::Error> {
|
|
|
|
|
knuffel::parse(filename, text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Config {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Config::parse(
|
|
|
|
|
"default-config.kdl",
|
|
|
|
|
include_str!("../resources/default-config.kdl"),
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 08:35:24 +04:00
|
|
|
impl FromStr for Mode {
|
|
|
|
|
type Err = miette::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
impl FromStr for Key {
|
|
|
|
|
type Err = miette::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
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);
|
2023-09-24 11:04:30 +04:00
|
|
|
if keysym.raw() == KEY_NoSymbol {
|
2023-09-05 12:58:51 +04:00
|
|
|
return Err(miette!("invalid key: {key}"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Key { keysym, modifiers })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
impl FromStr for SizeChange {
|
|
|
|
|
type Err = miette::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
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")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2023-10-03 07:41:05 +04:00
|
|
|
use miette::NarratableReportHandler;
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[track_caller]
|
|
|
|
|
fn check(text: &str, expected: Config) {
|
2023-10-03 07:41:05 +04:00
|
|
|
let _ = miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new())));
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
let parsed = Config::parse("test.kdl", text)
|
|
|
|
|
.map_err(miette::Report::new)
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(parsed, expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse() {
|
|
|
|
|
check(
|
|
|
|
|
r#"
|
|
|
|
|
input {
|
|
|
|
|
keyboard {
|
2023-09-16 20:01:52 +04:00
|
|
|
repeat-delay 600
|
2023-09-16 12:37:23 +04:00
|
|
|
repeat-rate 25
|
2023-11-02 00:10:22 +04:00
|
|
|
track-layout "window"
|
2023-09-05 12:58:51 +04:00
|
|
|
xkb {
|
|
|
|
|
layout "us,ru"
|
|
|
|
|
options "grp:win_space_toggle"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
touchpad {
|
|
|
|
|
tap
|
|
|
|
|
accel-speed 0.2
|
|
|
|
|
}
|
2023-10-03 17:02:07 +04:00
|
|
|
|
|
|
|
|
tablet {
|
|
|
|
|
map-to-output "eDP-1"
|
|
|
|
|
}
|
2023-12-28 09:36:10 +04:00
|
|
|
|
|
|
|
|
disable-power-key-handling
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
output "eDP-1" {
|
|
|
|
|
scale 2.0
|
2023-09-30 11:33:02 +04:00
|
|
|
position x=10 y=20
|
2023-10-03 08:35:24 +04:00
|
|
|
mode "1920x1080@144"
|
2023-09-21 13:48:32 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-21 19:58:03 +04:00
|
|
|
spawn-at-startup "alacritty" "-e" "fish"
|
|
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
focus-ring {
|
|
|
|
|
width 5
|
2023-10-31 09:07:11 +04:00
|
|
|
active-color 0 100 200 255
|
|
|
|
|
inactive-color 255 200 100 0
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-12-27 21:51:42 +04:00
|
|
|
border {
|
|
|
|
|
width 3
|
|
|
|
|
active-color 0 100 200 255
|
|
|
|
|
inactive-color 255 200 100 0
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 13:44:37 +04:00
|
|
|
prefer-no-csd
|
|
|
|
|
|
2023-10-01 17:42:56 +04:00
|
|
|
cursor {
|
|
|
|
|
xcursor-theme "breeze_cursors"
|
|
|
|
|
xcursor-size 16
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
preset-column-widths {
|
|
|
|
|
proportion 0.25
|
|
|
|
|
proportion 0.5
|
|
|
|
|
fixed 960
|
|
|
|
|
fixed 1280
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-02 21:07:29 +04:00
|
|
|
default-column-width { proportion 0.25; }
|
|
|
|
|
|
2023-10-07 17:45:55 +04:00
|
|
|
gaps 8
|
|
|
|
|
|
2023-12-21 08:37:30 +04:00
|
|
|
struts {
|
|
|
|
|
left 1
|
|
|
|
|
right 2
|
|
|
|
|
top 3
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-31 14:23:54 +04:00
|
|
|
screenshot-path "~/Screenshots/screenshot.png"
|
2023-10-31 08:57:44 +04:00
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
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; }
|
2023-09-16 12:14:02 +04:00
|
|
|
Mod+1 { focus-workspace 1;}
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
2023-09-06 15:49:46 +04:00
|
|
|
|
|
|
|
|
debug {
|
|
|
|
|
animation-slowdown 2.0
|
|
|
|
|
}
|
2023-09-05 12:58:51 +04:00
|
|
|
"#,
|
|
|
|
|
Config {
|
|
|
|
|
input: Input {
|
|
|
|
|
keyboard: Keyboard {
|
|
|
|
|
xkb: Xkb {
|
|
|
|
|
layout: Some("us,ru".to_owned()),
|
|
|
|
|
options: Some("grp:win_space_toggle".to_owned()),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
2023-09-16 20:01:52 +04:00
|
|
|
repeat_delay: 600,
|
2023-09-16 12:37:23 +04:00
|
|
|
repeat_rate: 25,
|
2023-11-02 00:10:22 +04:00
|
|
|
track_layout: TrackLayout::Window,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
touchpad: Touchpad {
|
|
|
|
|
tap: true,
|
|
|
|
|
natural_scroll: false,
|
|
|
|
|
accel_speed: 0.2,
|
|
|
|
|
},
|
2023-10-03 17:02:07 +04:00
|
|
|
tablet: Tablet {
|
|
|
|
|
map_to_output: Some("eDP-1".to_owned()),
|
|
|
|
|
},
|
2023-12-28 09:36:10 +04:00
|
|
|
disable_power_key_handling: true,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
2023-09-21 13:48:32 +04:00
|
|
|
outputs: vec![Output {
|
2023-12-18 10:27:41 +04:00
|
|
|
off: false,
|
2023-09-21 13:48:32 +04:00
|
|
|
name: "eDP-1".to_owned(),
|
|
|
|
|
scale: 2.,
|
2023-09-30 11:33:02 +04:00
|
|
|
position: Some(Position { x: 10, y: 20 }),
|
2023-10-03 08:35:24 +04:00
|
|
|
mode: Some(Mode {
|
|
|
|
|
width: 1920,
|
|
|
|
|
height: 1080,
|
|
|
|
|
refresh: Some(144.),
|
|
|
|
|
}),
|
2023-09-21 13:48:32 +04:00
|
|
|
}],
|
2023-09-21 19:58:03 +04:00
|
|
|
spawn_at_startup: vec![SpawnAtStartup {
|
|
|
|
|
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
|
|
|
|
|
}],
|
2023-09-26 13:09:33 +04:00
|
|
|
focus_ring: FocusRing {
|
|
|
|
|
off: false,
|
|
|
|
|
width: 5,
|
|
|
|
|
active_color: Color {
|
2023-10-31 09:07:11 +04:00
|
|
|
r: 0,
|
|
|
|
|
g: 100,
|
|
|
|
|
b: 200,
|
|
|
|
|
a: 255,
|
2023-09-26 13:09:33 +04:00
|
|
|
},
|
|
|
|
|
inactive_color: Color {
|
2023-10-31 09:07:11 +04:00
|
|
|
r: 255,
|
|
|
|
|
g: 200,
|
|
|
|
|
b: 100,
|
|
|
|
|
a: 0,
|
2023-09-26 13:09:33 +04:00
|
|
|
},
|
|
|
|
|
},
|
2023-12-27 21:51:42 +04:00
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-09-26 13:44:37 +04:00
|
|
|
prefer_no_csd: true,
|
2023-10-01 17:42:56 +04:00
|
|
|
cursor: Cursor {
|
|
|
|
|
xcursor_theme: String::from("breeze_cursors"),
|
|
|
|
|
xcursor_size: 16,
|
|
|
|
|
},
|
2023-10-05 09:25:07 +04:00
|
|
|
preset_column_widths: vec![
|
|
|
|
|
PresetWidth::Proportion(0.25),
|
|
|
|
|
PresetWidth::Proportion(0.5),
|
|
|
|
|
PresetWidth::Fixed(960),
|
|
|
|
|
PresetWidth::Fixed(1280),
|
|
|
|
|
],
|
2023-11-02 21:07:29 +04:00
|
|
|
default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion(0.25)])),
|
2023-10-07 17:45:55 +04:00
|
|
|
gaps: 8,
|
2023-12-21 08:37:30 +04:00
|
|
|
struts: Struts {
|
|
|
|
|
left: 1,
|
|
|
|
|
right: 2,
|
|
|
|
|
top: 3,
|
|
|
|
|
bottom: 0,
|
|
|
|
|
},
|
2023-10-31 14:23:54 +04:00
|
|
|
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
|
2023-09-05 12:58:51 +04:00
|
|
|
binds: Binds(vec![
|
|
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
2023-09-24 11:04:30 +04:00
|
|
|
keysym: Keysym::t,
|
2023-09-05 12:58:51 +04:00
|
|
|
modifiers: Modifiers::COMPOSITOR,
|
|
|
|
|
},
|
|
|
|
|
actions: vec![Action::Spawn(vec!["alacritty".to_owned()])],
|
|
|
|
|
},
|
|
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
2023-09-24 11:04:30 +04:00
|
|
|
keysym: Keysym::q,
|
2023-09-05 12:58:51 +04:00
|
|
|
modifiers: Modifiers::COMPOSITOR,
|
|
|
|
|
},
|
|
|
|
|
actions: vec![Action::CloseWindow],
|
|
|
|
|
},
|
|
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
2023-09-24 11:04:30 +04:00
|
|
|
keysym: Keysym::h,
|
2023-09-05 12:58:51 +04:00
|
|
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
|
|
|
|
},
|
|
|
|
|
actions: vec![Action::FocusMonitorLeft],
|
|
|
|
|
},
|
|
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
2023-09-24 11:04:30 +04:00
|
|
|
keysym: Keysym::l,
|
2023-09-05 12:58:51 +04:00
|
|
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
|
|
|
|
|
},
|
|
|
|
|
actions: vec![Action::MoveWindowToMonitorRight],
|
|
|
|
|
},
|
|
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
2023-09-24 11:04:30 +04:00
|
|
|
keysym: Keysym::comma,
|
2023-09-05 12:58:51 +04:00
|
|
|
modifiers: Modifiers::COMPOSITOR,
|
|
|
|
|
},
|
|
|
|
|
actions: vec![Action::ConsumeWindowIntoColumn],
|
|
|
|
|
},
|
2023-09-16 12:14:02 +04:00
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
2023-09-24 11:04:30 +04:00
|
|
|
keysym: Keysym::_1,
|
2023-09-16 12:14:02 +04:00
|
|
|
modifiers: Modifiers::COMPOSITOR,
|
|
|
|
|
},
|
|
|
|
|
actions: vec![Action::FocusWorkspace(1)],
|
|
|
|
|
},
|
2023-09-05 12:58:51 +04:00
|
|
|
]),
|
2023-09-06 15:49:46 +04:00
|
|
|
debug: DebugConfig {
|
|
|
|
|
animation_slowdown: 2.,
|
2023-09-08 17:54:02 +04:00
|
|
|
..Default::default()
|
2023-09-06 15:49:46 +04:00
|
|
|
},
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_create_default_config() {
|
|
|
|
|
let _ = Config::default();
|
|
|
|
|
}
|
2023-10-03 08:35:24 +04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_mode() {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"2560x1600@165.004".parse::<Mode>().unwrap(),
|
|
|
|
|
Mode {
|
|
|
|
|
width: 2560,
|
|
|
|
|
height: 1600,
|
|
|
|
|
refresh: Some(165.004),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"1920x1080".parse::<Mode>().unwrap(),
|
|
|
|
|
Mode {
|
|
|
|
|
width: 1920,
|
|
|
|
|
height: 1080,
|
|
|
|
|
refresh: None,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!("1920".parse::<Mode>().is_err());
|
|
|
|
|
assert!("1920x".parse::<Mode>().is_err());
|
|
|
|
|
assert!("1920x1080@".parse::<Mode>().is_err());
|
|
|
|
|
assert!("1920x1080@60Hz".parse::<Mode>().is_err());
|
|
|
|
|
}
|
2023-10-03 11:38:42 +04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_size_change() {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"10".parse::<SizeChange>().unwrap(),
|
|
|
|
|
SizeChange::SetFixed(10),
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"+10".parse::<SizeChange>().unwrap(),
|
|
|
|
|
SizeChange::AdjustFixed(10),
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"-10".parse::<SizeChange>().unwrap(),
|
|
|
|
|
SizeChange::AdjustFixed(-10),
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"10%".parse::<SizeChange>().unwrap(),
|
|
|
|
|
SizeChange::SetProportion(10.),
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"+10%".parse::<SizeChange>().unwrap(),
|
|
|
|
|
SizeChange::AdjustProportion(10.),
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"-10%".parse::<SizeChange>().unwrap(),
|
|
|
|
|
SizeChange::AdjustProportion(-10.),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!("-".parse::<SizeChange>().is_err());
|
|
|
|
|
assert!("10% ".parse::<SizeChange>().is_err());
|
|
|
|
|
}
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|