Files
niri/niri-config/src/lib.rs
T

2607 lines
83 KiB
Rust
Raw Normal View History

2024-01-07 09:07:22 +04:00
#[macro_use]
extern crate tracing;
use std::collections::HashSet;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
2023-09-05 12:58:51 +04:00
use std::str::FromStr;
2024-03-22 20:47:40 +04:00
use std::time::Duration;
2023-09-05 12:58:51 +04:00
use bitflags::bitflags;
use knuffel::errors::DecodeError;
2024-04-17 14:06:32 +04:00
use knuffel::Decode as _;
2024-01-07 09:07:22 +04:00
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
2024-05-05 10:19:47 +04:00
use niri_ipc::{ConfiguredMode, LayoutSwitchTarget, SizeChange, Transform};
2024-02-13 17:46:37 +04:00
use regex::Regex;
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};
2024-01-08 10:15:29 +04:00
use smithay::reexports::input;
2023-09-05 12:58:51 +04:00
#[derive(knuffel::Decode, Debug, PartialEq)]
pub struct Config {
#[knuffel(child, default)]
pub input: Input,
#[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)]
pub layout: Layout,
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-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)]
pub hotkey_overlay: HotkeyOverlay,
#[knuffel(child, default)]
2024-02-07 17:05:15 +04:00
pub animations: Animations,
2024-02-24 10:08:56 +04:00
#[knuffel(child, default)]
pub environment: Environment,
2024-02-13 17:46:37 +04:00
#[knuffel(children(name = "window-rule"))]
pub window_rules: Vec<WindowRule>,
2024-02-07 17:05:15 +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)]
2024-01-08 11:53:34 +04:00
pub mouse: Mouse,
#[knuffel(child, default)]
pub trackpoint: Trackpoint,
#[knuffel(child, default)]
2023-10-03 17:02:07 +04:00
pub tablet: Tablet,
2024-02-24 18:32:13 +01:00
#[knuffel(child, default)]
pub touch: Touch,
2023-12-28 09:36:10 +04:00
#[knuffel(child)]
pub disable_power_key_handling: bool,
2024-02-26 18:47:46 +01:00
#[knuffel(child)]
pub warp_mouse_to_focus: bool,
2024-03-18 18:17:04 +04:00
#[knuffel(child)]
pub focus_follows_mouse: bool,
2024-03-19 14:27:52 +00:00
#[knuffel(child)]
pub workspace_auto_back_and_forth: 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)]
pub repeat_delay: u16,
#[knuffel(child, unwrap(argument), default = 25)]
pub repeat_rate: u8,
#[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,
2024-03-23 10:10:01 +04:00
#[knuffel(child, unwrap(argument), default)]
pub layout: String,
2023-09-05 12:58:51 +04:00
#[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,
2024-03-23 10:10:01 +04:00
layout: &self.layout,
2023-12-05 08:04:46 +04:00
variant: &self.variant,
options: self.options.clone(),
}
}
}
2024-01-08 17:17:19 +04:00
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum CenterFocusedColumn {
/// Focusing a column will not center the column.
#[default]
Never,
/// The focused column will always be centered.
Always,
/// Focusing a column will center it if it doesn't fit on the screen together with the
/// previously focused column.
OnOverflow,
}
#[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)]
2024-01-08 10:24:00 +04:00
pub dwt: bool,
#[knuffel(child)]
2024-02-03 08:20:15 +04:00
pub dwtp: bool,
#[knuffel(child)]
2023-09-05 12:58:51 +04:00
pub natural_scroll: bool,
2024-03-13 21:26:03 -07:00
#[knuffel(child, unwrap(argument, str))]
pub click_method: Option<ClickMethod>,
2023-09-05 12:58:51 +04:00
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: f64,
2024-01-08 10:15:29 +04:00
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
2024-01-08 10:32:04 +04:00
#[knuffel(child, unwrap(argument, str))]
pub tap_button_map: Option<TapButtonMap>,
2024-01-08 10:15:29 +04:00
}
2024-01-08 11:53:34 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Mouse {
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: f64,
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
}
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Trackpoint {
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: f64,
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
}
2024-03-13 21:26:03 -07:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClickMethod {
Clickfinger,
ButtonAreas,
}
impl From<ClickMethod> for input::ClickMethod {
fn from(value: ClickMethod) -> Self {
match value {
ClickMethod::Clickfinger => Self::Clickfinger,
ClickMethod::ButtonAreas => Self::ButtonAreas,
}
}
}
2024-01-08 10:15:29 +04:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccelProfile {
Adaptive,
Flat,
}
impl From<AccelProfile> for input::AccelProfile {
fn from(value: AccelProfile) -> Self {
match value {
AccelProfile::Adaptive => Self::Adaptive,
AccelProfile::Flat => Self::Flat,
}
}
2023-09-05 12:58:51 +04:00
}
2024-01-08 10:32:04 +04:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TapButtonMap {
LeftRightMiddle,
LeftMiddleRight,
}
impl From<TapButtonMap> for input::TapButtonMap {
fn from(value: TapButtonMap) -> Self {
match value {
TapButtonMap::LeftRightMiddle => Self::LeftRightMiddle,
TapButtonMap::LeftMiddleRight => Self::LeftMiddleRight,
}
}
}
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>,
}
2024-02-24 18:32:13 +01:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Touch {
#[knuffel(child, unwrap(argument))]
pub map_to_output: Option<String>,
}
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Output {
2023-12-18 10:27:41 +04:00
#[knuffel(child)]
pub off: bool,
#[knuffel(argument)]
pub name: String,
#[knuffel(child, unwrap(argument), default = 1.)]
pub scale: f64,
2024-01-28 14:25:40 +01:00
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
pub transform: Transform,
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))]
2024-05-05 10:19:47 +04:00
pub mode: Option<ConfiguredMode>,
2024-04-14 09:23:15 +04:00
#[knuffel(child)]
pub variable_refresh_rate: bool,
}
impl Default for Output {
fn default() -> Self {
Self {
2023-12-18 10:27:41 +04:00
off: false,
name: String::new(),
scale: 1.,
2024-01-28 14:25:40 +01:00
transform: Transform::Normal,
2023-09-30 11:33:02 +04:00
position: None,
2023-10-03 08:35:24 +04:00
mode: None,
2024-04-14 09:23:15 +04:00
variable_refresh_rate: false,
}
}
}
2024-01-16 08:43:28 +04:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
2023-09-30 11:33:02 +04:00
pub struct Position {
#[knuffel(property)]
pub x: i32,
#[knuffel(property)]
pub y: i32,
}
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Layout {
#[knuffel(child, default)]
pub focus_ring: FocusRing,
#[knuffel(child, default)]
pub border: Border,
#[knuffel(child, unwrap(children), default)]
pub preset_column_widths: Vec<PresetWidth>,
#[knuffel(child)]
pub default_column_width: Option<DefaultColumnWidth>,
2024-01-08 17:17:19 +04:00
#[knuffel(child, unwrap(argument), default)]
pub center_focused_column: CenterFocusedColumn,
#[knuffel(child, unwrap(argument), default = Self::default().gaps)]
pub gaps: u16,
#[knuffel(child, default)]
pub struts: Struts,
}
impl Default for Layout {
fn default() -> Self {
Self {
focus_ring: Default::default(),
border: Default::default(),
preset_column_widths: Default::default(),
default_column_width: Default::default(),
center_focused_column: Default::default(),
gaps: 16,
struts: Default::default(),
}
}
}
2023-09-21 19:58:03 +04:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct SpawnAtStartup {
#[knuffel(arguments)]
pub command: Vec<String>,
}
#[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 = Self::default().width)]
2023-09-26 13:09:33 +04:00
pub width: u16,
#[knuffel(child, default = Self::default().active_color)]
2023-09-26 13:09:33 +04:00
pub active_color: Color,
#[knuffel(child, default = Self::default().inactive_color)]
2023-09-26 13:09:33 +04:00
pub inactive_color: Color,
2024-02-21 21:27:44 +04:00
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
2023-09-26 13:09:33 +04:00
}
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),
2024-02-21 21:27:44 +04:00
active_gradient: None,
inactive_gradient: None,
2023-09-26 13:09:33 +04:00
}
}
}
2024-02-21 21:27:44 +04:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Gradient {
#[knuffel(property, str)]
pub from: Color,
#[knuffel(property, str)]
pub to: Color,
#[knuffel(property, default = 180)]
pub angle: i16,
#[knuffel(property, default)]
pub relative_to: GradientRelativeTo,
}
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum GradientRelativeTo {
#[default]
Window,
WorkspaceView,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Border {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = Self::default().width)]
pub width: u16,
#[knuffel(child, default = Self::default().active_color)]
pub active_color: Color,
#[knuffel(child, default = Self::default().inactive_color)]
pub inactive_color: Color,
2024-02-21 21:27:44 +04:00
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
}
impl Default for Border {
fn default() -> Self {
Self {
2024-02-12 09:22:22 +04:00
off: true,
width: 4,
active_color: Color::new(255, 200, 127, 255),
inactive_color: Color::new(80, 80, 80, 255),
2024-02-21 21:27:44 +04:00
active_gradient: None,
inactive_gradient: None,
2024-02-12 09:22:22 +04:00
}
}
}
impl From<Border> for FocusRing {
fn from(value: Border) -> Self {
Self {
off: value.off,
width: value.width,
active_color: value.active_color,
inactive_color: value.inactive_color,
2024-02-21 21:27:44 +04:00
active_gradient: value.active_gradient,
inactive_gradient: value.inactive_gradient,
}
}
}
2024-04-24 22:17:53 +04:00
impl From<FocusRing> for Border {
fn from(value: FocusRing) -> Self {
Self {
off: value.off,
width: value.width,
active_color: value.active_color,
inactive_color: value.inactive_color,
active_gradient: value.active_gradient,
inactive_gradient: value.inactive_gradient,
}
}
}
2024-02-26 09:02:47 +04:00
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
2023-09-26 13:09:33 +04:00
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
2023-09-26 13:09:33 +04:00
}
impl Color {
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 {
let [r, g, b, a] = [c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.);
[r * a, g * a, b * a, a]
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,
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub enum PresetWidth {
Proportion(#[knuffel(argument)] f64),
Fixed(#[knuffel(argument)] i32),
}
#[derive(Debug, Clone, PartialEq)]
pub struct DefaultColumnWidth(pub Option<PresetWidth>);
2023-11-02 21:07:29 +04:00
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,
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct HotkeyOverlay {
#[knuffel(child)]
pub skip_at_startup: bool,
}
2024-04-21 20:10:35 +04:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
2024-02-07 17:05:15 +04:00
pub struct Animations {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = 1.)]
pub slowdown: f64,
2024-04-17 14:06:32 +04:00
#[knuffel(child, default)]
pub workspace_switch: WorkspaceSwitchAnim,
#[knuffel(child, default)]
pub window_open: WindowOpenAnim,
#[knuffel(child, default)]
pub window_close: WindowCloseAnim,
#[knuffel(child, default)]
2024-04-18 17:36:12 +04:00
pub horizontal_view_movement: HorizontalViewMovementAnim,
#[knuffel(child, default)]
pub window_movement: WindowMovementAnim,
#[knuffel(child, default)]
2024-04-17 14:06:32 +04:00
pub window_resize: WindowResizeAnim,
#[knuffel(child, default)]
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
2024-02-07 17:05:15 +04:00
}
impl Default for Animations {
fn default() -> Self {
Self {
off: false,
slowdown: 1.,
2024-04-17 14:06:32 +04:00
workspace_switch: Default::default(),
horizontal_view_movement: Default::default(),
window_movement: Default::default(),
window_open: Default::default(),
window_close: Default::default(),
window_resize: Default::default(),
config_notification_open_close: Default::default(),
2024-02-07 17:05:15 +04:00
}
}
}
2024-03-05 13:32:30 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-04-17 14:06:32 +04:00
pub struct WorkspaceSwitchAnim(pub Animation);
2024-02-07 17:05:15 +04:00
2024-04-17 14:06:32 +04:00
impl Default for WorkspaceSwitchAnim {
fn default() -> Self {
Self(Animation {
2024-03-05 13:32:30 +04:00
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 1000,
epsilon: 0.0001,
}),
2024-04-17 14:06:32 +04:00
})
2024-02-07 17:05:15 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-02-07 17:05:15 +04:00
2024-04-17 14:06:32 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-04-18 17:36:12 +04:00
pub struct WindowOpenAnim(pub Animation);
2024-04-17 14:06:32 +04:00
2024-04-18 17:36:12 +04:00
impl Default for WindowOpenAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
Self(Animation {
2024-03-05 13:32:30 +04:00
off: false,
2024-04-18 17:36:12 +04:00
kind: AnimationKind::Easing(EasingParams {
duration_ms: 150,
curve: AnimationCurve::EaseOutExpo,
2024-04-08 18:23:18 +04:00
}),
2024-04-17 14:06:32 +04:00
})
2024-04-08 18:23:18 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-04-08 18:23:18 +04:00
2024-04-17 14:06:32 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-04-18 17:36:12 +04:00
pub struct WindowCloseAnim(pub Animation);
2024-04-17 14:06:32 +04:00
2024-04-18 17:36:12 +04:00
impl Default for WindowCloseAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
Self(Animation {
2024-04-08 18:23:18 +04:00
off: false,
2024-04-18 17:36:12 +04:00
kind: AnimationKind::Easing(EasingParams {
duration_ms: 150,
curve: AnimationCurve::EaseOutQuad,
2024-03-05 13:32:30 +04:00
}),
2024-04-17 14:06:32 +04:00
})
2024-02-07 17:05:15 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-02-07 17:05:15 +04:00
2024-04-17 14:06:32 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-04-18 17:36:12 +04:00
pub struct HorizontalViewMovementAnim(pub Animation);
2024-04-17 14:06:32 +04:00
2024-04-18 17:36:12 +04:00
impl Default for HorizontalViewMovementAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
Self(Animation {
2024-03-05 13:32:30 +04:00
off: false,
kind: AnimationKind::Spring(SpringParams {
2024-04-18 17:36:12 +04:00
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
2024-03-05 13:32:30 +04:00
}),
2024-04-17 14:06:32 +04:00
})
2024-02-07 17:05:15 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-02-07 17:05:15 +04:00
2024-04-17 14:06:32 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-04-18 17:36:12 +04:00
pub struct WindowMovementAnim(pub Animation);
2024-04-17 14:06:32 +04:00
2024-04-18 17:36:12 +04:00
impl Default for WindowMovementAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
Self(Animation {
2024-03-05 13:32:30 +04:00
off: false,
2024-04-18 17:36:12 +04:00
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
2024-03-05 13:32:30 +04:00
}),
2024-04-17 14:06:32 +04:00
})
2024-03-05 13:32:30 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-04-09 22:37:10 +04:00
2024-04-21 20:10:35 +04:00
#[derive(Debug, Clone, PartialEq)]
pub struct WindowResizeAnim {
pub anim: Animation,
pub custom_shader: Option<String>,
}
2024-04-17 14:06:32 +04:00
2024-04-18 17:36:12 +04:00
impl Default for WindowResizeAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
2024-04-21 20:10:35 +04:00
Self {
anim: Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
},
custom_shader: None,
}
2024-04-09 22:37:10 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-04-13 11:07:23 +04:00
2024-04-17 14:06:32 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-04-18 17:36:12 +04:00
pub struct ConfigNotificationOpenCloseAnim(pub Animation);
2024-04-17 14:06:32 +04:00
2024-04-18 17:36:12 +04:00
impl Default for ConfigNotificationOpenCloseAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
Self(Animation {
2024-04-13 11:07:23 +04:00
off: false,
kind: AnimationKind::Spring(SpringParams {
2024-04-18 17:36:12 +04:00
damping_ratio: 0.6,
stiffness: 1000,
epsilon: 0.001,
2024-04-13 11:07:23 +04:00
}),
2024-04-17 14:06:32 +04:00
})
2024-04-13 11:07:23 +04:00
}
2024-03-05 13:32:30 +04:00
}
2024-04-17 14:06:32 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Animation {
pub off: bool,
pub kind: AnimationKind,
}
2024-03-05 13:32:30 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnimationKind {
Easing(EasingParams),
Spring(SpringParams),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EasingParams {
2024-04-17 14:06:32 +04:00
pub duration_ms: u32,
pub curve: AnimationCurve,
2024-02-07 17:05:15 +04:00
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
pub enum AnimationCurve {
2024-04-09 21:36:19 +04:00
EaseOutQuad,
2024-02-07 17:05:15 +04:00
EaseOutCubic,
EaseOutExpo,
}
2024-03-05 13:32:30 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpringParams {
pub damping_ratio: f64,
pub stiffness: u32,
pub epsilon: f64,
}
2024-02-24 10:08:56 +04:00
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq, Eq)]
pub struct Environment(#[knuffel(children)] pub Vec<EnvironmentVariable>);
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct EnvironmentVariable {
#[knuffel(node_name)]
pub name: String,
#[knuffel(argument)]
pub value: Option<String>,
}
2024-02-13 17:46:37 +04:00
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
pub struct WindowRule {
#[knuffel(children(name = "match"))]
pub matches: Vec<Match>,
#[knuffel(children(name = "exclude"))]
pub excludes: Vec<Match>,
// Rules applied at initial configure.
2024-02-13 17:46:37 +04:00
#[knuffel(child)]
pub default_column_width: Option<DefaultColumnWidth>,
#[knuffel(child, unwrap(argument))]
pub open_on_output: Option<String>,
2024-02-23 14:24:39 +04:00
#[knuffel(child, unwrap(argument))]
pub open_maximized: Option<bool>,
2024-02-24 08:44:21 +04:00
#[knuffel(child, unwrap(argument))]
pub open_fullscreen: Option<bool>,
// Rules applied dynamically.
#[knuffel(child, unwrap(argument))]
pub min_width: Option<u16>,
#[knuffel(child, unwrap(argument))]
pub min_height: Option<u16>,
#[knuffel(child, unwrap(argument))]
pub max_width: Option<u16>,
#[knuffel(child, unwrap(argument))]
pub max_height: Option<u16>,
2024-04-24 22:17:53 +04:00
#[knuffel(child, default)]
pub focus_ring: BorderRule,
2024-04-24 21:49:07 +04:00
#[knuffel(child, default)]
pub border: BorderRule,
#[knuffel(child, unwrap(argument))]
pub draw_border_with_background: Option<bool>,
2024-03-24 08:30:26 +04:00
#[knuffel(child, unwrap(argument))]
pub opacity: Option<f32>,
2024-05-01 19:06:08 +04:00
#[knuffel(child)]
pub geometry_corner_radius: Option<CornerRadius>,
#[knuffel(child, unwrap(argument))]
pub clip_to_geometry: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub block_out_from: Option<BlockOutFrom>,
2024-02-13 17:46:37 +04:00
}
2024-03-23 14:38:07 +04:00
// Remember to update the PartialEq impl when adding fields!
2024-02-13 17:46:37 +04:00
#[derive(knuffel::Decode, Debug, Default, Clone)]
pub struct Match {
#[knuffel(property, str)]
pub app_id: Option<Regex>,
#[knuffel(property, str)]
pub title: Option<Regex>,
2024-03-23 14:38:07 +04:00
#[knuffel(property)]
pub is_active: Option<bool>,
2024-03-23 16:16:52 +04:00
#[knuffel(property)]
pub is_focused: Option<bool>,
2024-02-13 17:46:37 +04:00
}
impl PartialEq for Match {
fn eq(&self, other: &Self) -> bool {
2024-03-23 14:38:07 +04:00
self.is_active == other.is_active
2024-03-23 16:16:52 +04:00
&& self.is_focused == other.is_focused
2024-03-23 14:38:07 +04:00
&& self.app_id.as_ref().map(Regex::as_str) == other.app_id.as_ref().map(Regex::as_str)
2024-02-13 17:46:37 +04:00
&& self.title.as_ref().map(Regex::as_str) == other.title.as_ref().map(Regex::as_str)
}
}
2024-05-01 19:06:08 +04:00
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct CornerRadius {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl From<CornerRadius> for [f32; 4] {
fn from(value: CornerRadius) -> Self {
[
value.top_left,
value.top_right,
value.bottom_right,
value.bottom_left,
]
}
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockOutFrom {
Screencast,
ScreenCapture,
}
2024-04-24 21:49:07 +04:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct BorderRule {
#[knuffel(child)]
pub off: bool,
#[knuffel(child)]
pub on: bool,
#[knuffel(child, unwrap(argument))]
pub width: Option<u16>,
#[knuffel(child)]
pub active_color: Option<Color>,
#[knuffel(child)]
pub inactive_color: Option<Color>,
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
}
#[derive(Debug, Default, PartialEq)]
pub struct Binds(pub Vec<Bind>);
2023-09-05 12:58:51 +04:00
2024-03-22 20:27:58 +04:00
#[derive(Debug, Clone, PartialEq)]
2023-09-05 12:58:51 +04:00
pub struct Bind {
pub key: Key,
pub action: Action,
2024-03-22 20:47:40 +04:00
pub cooldown: Option<Duration>,
pub allow_when_locked: bool,
2023-09-05 12:58:51 +04:00
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
2023-09-05 12:58:51 +04:00
pub struct Key {
2024-03-22 10:36:19 +04:00
pub trigger: Trigger,
2023-09-05 12:58:51 +04:00
pub modifiers: Modifiers,
}
2024-03-22 10:36:19 +04:00
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Trigger {
Keysym(Keysym),
WheelScrollDown,
WheelScrollUp,
WheelScrollLeft,
WheelScrollRight,
2024-03-23 20:23:21 +04:00
TouchpadScrollDown,
TouchpadScrollUp,
TouchpadScrollLeft,
TouchpadScrollRight,
2024-03-22 10:36:19 +04:00
}
2023-09-05 12:58:51 +04:00
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2023-09-05 12:58:51 +04:00
pub struct Modifiers : u8 {
const CTRL = 1;
const SHIFT = 2;
const ALT = 4;
const SUPER = 8;
const ISO_LEVEL3_SHIFT = 16;
const COMPOSITOR = 32;
2023-09-05 12:58:51 +04:00
}
}
2024-02-10 09:33:32 +04:00
// Remember to add new actions to the CLI enum too.
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(property(name = "skip-confirmation"), default)] bool),
2023-09-05 12:58:51 +04:00
#[knuffel(skip)]
ChangeVt(i32),
Suspend,
2023-10-09 18:37:43 +04:00
PowerOffMonitors,
2023-09-05 12:58:51 +04:00
ToggleDebugTint,
2024-05-02 17:52:06 +04:00
DebugToggleOpaqueRegions,
DebugToggleDamage,
2023-09-05 12:58:51 +04:00
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,
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,
MoveWindowDownOrToWorkspaceDown,
MoveWindowUpOrToWorkspaceUp,
ConsumeOrExpelWindowLeft,
ConsumeOrExpelWindowRight,
2023-09-05 12:58:51 +04:00
ConsumeWindowIntoColumn,
ExpelWindowFromColumn,
CenterColumn,
2023-09-05 12:58:51 +04:00
FocusWorkspaceDown,
FocusWorkspaceUp,
2023-09-16 12:14:02 +04:00
FocusWorkspace(#[knuffel(argument)] u8),
2024-03-19 14:27:52 +00:00
FocusWorkspacePrevious,
2023-09-05 12:58:51 +04:00
MoveWindowToWorkspaceDown,
MoveWindowToWorkspaceUp,
2023-09-16 12:14:02 +04:00
MoveWindowToWorkspace(#[knuffel(argument)] u8),
2024-01-15 10:31:44 +04:00
MoveColumnToWorkspaceDown,
MoveColumnToWorkspaceUp,
MoveColumnToWorkspace(#[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,
2024-01-15 10:36:59 +04:00
MoveColumnToMonitorLeft,
MoveColumnToMonitorRight,
MoveColumnToMonitorDown,
MoveColumnToMonitorUp,
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),
2024-02-10 09:33:32 +04:00
SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget),
2024-01-18 19:20:46 +04:00
ShowHotkeyOverlay,
MoveWorkspaceToMonitorLeft,
MoveWorkspaceToMonitorRight,
MoveWorkspaceToMonitorDown,
MoveWorkspaceToMonitorUp,
2023-10-03 11:38:42 +04:00
}
2024-02-10 09:33:32 +04:00
impl From<niri_ipc::Action> for Action {
fn from(value: niri_ipc::Action) -> Self {
match value {
niri_ipc::Action::Quit { skip_confirmation } => Self::Quit(skip_confirmation),
2024-02-10 09:33:32 +04:00
niri_ipc::Action::PowerOffMonitors => Self::PowerOffMonitors,
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
niri_ipc::Action::Screenshot => Self::Screenshot,
niri_ipc::Action::ScreenshotScreen => Self::ScreenshotScreen,
niri_ipc::Action::ScreenshotWindow => Self::ScreenshotWindow,
niri_ipc::Action::CloseWindow => Self::CloseWindow,
niri_ipc::Action::FullscreenWindow => Self::FullscreenWindow,
niri_ipc::Action::FocusColumnLeft => Self::FocusColumnLeft,
niri_ipc::Action::FocusColumnRight => Self::FocusColumnRight,
niri_ipc::Action::FocusColumnFirst => Self::FocusColumnFirst,
niri_ipc::Action::FocusColumnLast => Self::FocusColumnLast,
niri_ipc::Action::FocusWindowDown => Self::FocusWindowDown,
niri_ipc::Action::FocusWindowUp => Self::FocusWindowUp,
niri_ipc::Action::FocusWindowOrWorkspaceDown => Self::FocusWindowOrWorkspaceDown,
niri_ipc::Action::FocusWindowOrWorkspaceUp => Self::FocusWindowOrWorkspaceUp,
niri_ipc::Action::MoveColumnLeft => Self::MoveColumnLeft,
niri_ipc::Action::MoveColumnRight => Self::MoveColumnRight,
niri_ipc::Action::MoveColumnToFirst => Self::MoveColumnToFirst,
niri_ipc::Action::MoveColumnToLast => Self::MoveColumnToLast,
niri_ipc::Action::MoveWindowDown => Self::MoveWindowDown,
niri_ipc::Action::MoveWindowUp => Self::MoveWindowUp,
niri_ipc::Action::MoveWindowDownOrToWorkspaceDown => {
Self::MoveWindowDownOrToWorkspaceDown
}
niri_ipc::Action::MoveWindowUpOrToWorkspaceUp => Self::MoveWindowUpOrToWorkspaceUp,
niri_ipc::Action::ConsumeOrExpelWindowLeft => Self::ConsumeOrExpelWindowLeft,
niri_ipc::Action::ConsumeOrExpelWindowRight => Self::ConsumeOrExpelWindowRight,
niri_ipc::Action::ConsumeWindowIntoColumn => Self::ConsumeWindowIntoColumn,
niri_ipc::Action::ExpelWindowFromColumn => Self::ExpelWindowFromColumn,
niri_ipc::Action::CenterColumn => Self::CenterColumn,
niri_ipc::Action::FocusWorkspaceDown => Self::FocusWorkspaceDown,
niri_ipc::Action::FocusWorkspaceUp => Self::FocusWorkspaceUp,
niri_ipc::Action::FocusWorkspace { index } => Self::FocusWorkspace(index),
2024-03-19 14:27:52 +00:00
niri_ipc::Action::FocusWorkspacePrevious => Self::FocusWorkspacePrevious,
2024-02-10 09:33:32 +04:00
niri_ipc::Action::MoveWindowToWorkspaceDown => Self::MoveWindowToWorkspaceDown,
niri_ipc::Action::MoveWindowToWorkspaceUp => Self::MoveWindowToWorkspaceUp,
niri_ipc::Action::MoveWindowToWorkspace { index } => Self::MoveWindowToWorkspace(index),
niri_ipc::Action::MoveColumnToWorkspaceDown => Self::MoveColumnToWorkspaceDown,
niri_ipc::Action::MoveColumnToWorkspaceUp => Self::MoveColumnToWorkspaceUp,
niri_ipc::Action::MoveColumnToWorkspace { index } => Self::MoveColumnToWorkspace(index),
niri_ipc::Action::MoveWorkspaceDown => Self::MoveWorkspaceDown,
niri_ipc::Action::MoveWorkspaceUp => Self::MoveWorkspaceUp,
niri_ipc::Action::FocusMonitorLeft => Self::FocusMonitorLeft,
niri_ipc::Action::FocusMonitorRight => Self::FocusMonitorRight,
niri_ipc::Action::FocusMonitorDown => Self::FocusMonitorDown,
niri_ipc::Action::FocusMonitorUp => Self::FocusMonitorUp,
niri_ipc::Action::MoveWindowToMonitorLeft => Self::MoveWindowToMonitorLeft,
niri_ipc::Action::MoveWindowToMonitorRight => Self::MoveWindowToMonitorRight,
niri_ipc::Action::MoveWindowToMonitorDown => Self::MoveWindowToMonitorDown,
niri_ipc::Action::MoveWindowToMonitorUp => Self::MoveWindowToMonitorUp,
niri_ipc::Action::MoveColumnToMonitorLeft => Self::MoveColumnToMonitorLeft,
niri_ipc::Action::MoveColumnToMonitorRight => Self::MoveColumnToMonitorRight,
niri_ipc::Action::MoveColumnToMonitorDown => Self::MoveColumnToMonitorDown,
niri_ipc::Action::MoveColumnToMonitorUp => Self::MoveColumnToMonitorUp,
niri_ipc::Action::SetWindowHeight { change } => Self::SetWindowHeight(change),
niri_ipc::Action::SwitchPresetColumnWidth => Self::SwitchPresetColumnWidth,
niri_ipc::Action::MaximizeColumn => Self::MaximizeColumn,
niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change),
niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout),
niri_ipc::Action::ShowHotkeyOverlay => Self::ShowHotkeyOverlay,
niri_ipc::Action::MoveWorkspaceToMonitorLeft => Self::MoveWorkspaceToMonitorLeft,
niri_ipc::Action::MoveWorkspaceToMonitorRight => Self::MoveWorkspaceToMonitorRight,
niri_ipc::Action::MoveWorkspaceToMonitorDown => Self::MoveWorkspaceToMonitorDown,
niri_ipc::Action::MoveWorkspaceToMonitorUp => Self::MoveWorkspaceToMonitorUp,
niri_ipc::Action::ToggleDebugTint => Self::ToggleDebugTint,
2024-05-02 17:52:06 +04:00
niri_ipc::Action::DebugToggleOpaqueRegions => Self::DebugToggleOpaqueRegions,
niri_ipc::Action::DebugToggleDamage => Self::DebugToggleDamage,
2024-02-10 09:33:32 +04:00
}
}
2023-11-02 00:09:31 +04:00
}
2024-02-07 17:05:15 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
2023-09-06 15:49:46 +04:00
pub struct DebugConfig {
#[knuffel(child, unwrap(argument))]
pub preview_render: Option<PreviewRender>,
2023-09-08 17:54:02 +04:00
#[knuffel(child)]
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,
#[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,
2024-04-25 22:10:52 +04:00
#[knuffel(child)]
pub disable_direct_scanout: bool,
2024-01-06 09:14:48 +04:00
#[knuffel(child, unwrap(argument))]
pub render_drm_device: Option<PathBuf>,
#[knuffel(child)]
pub emulate_zero_presentation_time: bool,
2023-09-06 15:49:46 +04:00
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PreviewRender {
Screencast,
ScreenCapture,
}
2023-09-05 12:58:51 +04:00
impl Config {
pub fn load(path: &Path) -> miette::Result<Self> {
2024-01-16 12:53:01 +04:00
let _span = tracy_client::span!("Config::load");
2024-01-07 09:07:22 +04:00
Self::load_internal(path).context("error loading config")
}
fn load_internal(path: &Path) -> miette::Result<Self> {
let contents = std::fs::read_to_string(path)
2023-09-05 12:58:51 +04:00
.into_diagnostic()
.with_context(|| format!("error reading {path:?}"))?;
let config = Self::parse(
path.file_name()
.and_then(OsStr::to_str)
.unwrap_or("config.kdl"),
&contents,
)
.context("error parsing")?;
2023-09-05 12:58:51 +04:00
debug!("loaded config from {path:?}");
Ok(config)
2023-09-05 12:58:51 +04:00
}
pub fn parse(filename: &str, text: &str) -> Result<Self, knuffel::Error> {
2024-01-16 12:53:01 +04:00
let _span = tracy_client::span!("Config::parse");
2023-09-05 12:58:51 +04:00
knuffel::parse(filename, text)
}
}
impl Default for Config {
fn default() -> Self {
Config::parse(
"default-config.kdl",
2024-01-07 09:07:22 +04:00
include_str!("../../resources/default-config.kdl"),
2023-09-05 12:58:51 +04:00
)
.unwrap()
}
}
2024-04-24 21:49:07 +04:00
impl BorderRule {
pub fn merge_with(&mut self, other: &Self) {
self.off |= other.off;
self.on |= other.on;
if let Some(x) = other.width {
self.width = Some(x);
}
if let Some(x) = other.active_color {
self.active_color = Some(x);
}
if let Some(x) = other.inactive_color {
self.inactive_color = Some(x);
}
if let Some(x) = other.active_gradient {
self.active_gradient = Some(x);
}
if let Some(x) = other.inactive_gradient {
self.inactive_gradient = Some(x);
}
}
pub fn resolve_against(&self, mut config: Border) -> Border {
config.off |= self.off;
if self.on {
config.off = false;
}
if let Some(x) = self.width {
config.width = x;
}
if let Some(x) = self.active_color {
config.active_color = x;
config.active_gradient = None;
}
if let Some(x) = self.inactive_color {
config.inactive_color = x;
config.inactive_gradient = None;
}
if let Some(x) = self.active_gradient {
config.active_gradient = Some(x);
}
if let Some(x) = self.inactive_gradient {
config.inactive_gradient = Some(x);
}
config
}
}
2024-05-01 19:06:08 +04:00
impl CornerRadius {
pub fn fit_to(self, width: f32, height: f32) -> Self {
// Like in CSS: https://drafts.csswg.org/css-backgrounds/#corner-overlap
let reduction = f32::min(
f32::min(
width / (self.top_left + self.top_right),
width / (self.bottom_left + self.bottom_right),
),
f32::min(
height / (self.top_left + self.bottom_left),
height / (self.top_right + self.bottom_right),
),
);
let reduction = f32::min(1., reduction);
Self {
top_left: self.top_left * reduction,
top_right: self.top_right * reduction,
bottom_right: self.bottom_right * reduction,
bottom_left: self.bottom_left * reduction,
}
}
2024-05-05 07:43:21 +04:00
pub fn expanded_by(mut self, width: f32) -> Self {
if self.top_left > 0. {
self.top_left += width;
2024-05-01 19:06:08 +04:00
}
2024-05-05 07:43:21 +04:00
if self.top_right > 0. {
self.top_right += width;
}
if self.bottom_right > 0. {
self.bottom_right += width;
2024-05-01 19:06:08 +04:00
}
2024-05-05 07:43:21 +04:00
if self.bottom_left > 0. {
self.bottom_left += width;
}
self
2024-05-01 19:06:08 +04:00
}
pub fn scaled_by(self, scale: f32) -> Self {
Self {
top_left: self.top_left * scale,
top_right: self.top_right * scale,
bottom_right: self.bottom_right * scale,
bottom_left: self.bottom_left * scale,
}
}
}
2024-02-21 21:27:44 +04:00
impl FromStr for Color {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let [r, g, b, a] = csscolorparser::parse(s).into_diagnostic()?.to_rgba8();
Ok(Self { r, g, b, a })
}
}
2024-02-26 09:02:47 +04:00
#[derive(knuffel::Decode)]
struct ColorRgba {
#[knuffel(argument)]
r: u8,
#[knuffel(argument)]
g: u8,
#[knuffel(argument)]
b: u8,
#[knuffel(argument)]
a: u8,
}
impl From<ColorRgba> for Color {
fn from(value: ColorRgba) -> Self {
let ColorRgba { r, g, b, a } = value;
Self { r, g, b, a }
}
}
// Manual impl to allow both one-argument string and 4-argument RGBA forms.
impl<S> knuffel::Decode<S> for Color
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
2024-02-26 09:02:47 +04:00
// Check for unexpected type name.
if let Some(type_name) = &node.type_name {
ctx.emit_error(DecodeError::unexpected(
2024-02-26 09:02:47 +04:00
type_name,
"type name",
"no type name expected for this node",
));
}
// Get the first argument.
let mut iter_args = node.arguments.iter();
let val = iter_args
.next()
.ok_or_else(|| DecodeError::missing(node, "additional argument is required"))?;
2024-02-26 09:02:47 +04:00
// Check for unexpected type name.
if let Some(typ) = &val.type_name {
ctx.emit_error(DecodeError::TypeName {
2024-02-26 09:02:47 +04:00
span: typ.span().clone(),
found: Some((**typ).clone()),
expected: knuffel::errors::ExpectedType::no_type(),
rust_type: "str",
});
}
// Check the argument type.
let rv = match *val.literal {
// If it's a string, use FromStr.
knuffel::ast::Literal::String(ref s) => {
Color::from_str(s).map_err(|e| DecodeError::conversion(&val.literal, e))
}
2024-02-26 09:02:47 +04:00
// Otherwise, fall back to the 4-argument RGBA form.
_ => return ColorRgba::decode_node(node, ctx).map(Color::from),
}?;
// Check for unexpected following arguments.
if let Some(val) = iter_args.next() {
ctx.emit_error(DecodeError::unexpected(
2024-02-26 09:02:47 +04:00
&val.literal,
"argument",
"unexpected argument",
));
}
// Check for unexpected properties and children.
for name in node.properties.keys() {
ctx.emit_error(DecodeError::unexpected(
2024-02-26 09:02:47 +04:00
name,
"property",
format!("unexpected property `{}`", name.escape_default()),
));
}
for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) {
ctx.emit_error(DecodeError::unexpected(
2024-02-26 09:02:47 +04:00
child,
"node",
format!("unexpected node `{}`", child.node_name.escape_default()),
));
}
Ok(rv)
}
}
fn expect_only_children<S>(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) where
S: knuffel::traits::ErrorSpan,
{
if let Some(type_name) = &node.type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
for val in node.arguments.iter() {
ctx.emit_error(DecodeError::unexpected(
&val.literal,
"argument",
"no arguments expected for this node",
))
}
for name in node.properties.keys() {
ctx.emit_error(DecodeError::unexpected(
name,
"property",
"no properties expected for this node",
))
}
}
impl<S> knuffel::Decode<S> for DefaultColumnWidth
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
expect_only_children(node, ctx);
let mut children = node.children();
if let Some(child) = children.next() {
if let Some(unwanted_child) = children.next() {
ctx.emit_error(DecodeError::unexpected(
unwanted_child,
"node",
"expected no more than one child",
));
}
PresetWidth::decode_node(child, ctx).map(Some).map(Self)
} else {
Ok(Self(None))
}
}
}
2024-03-05 13:32:30 +04:00
fn parse_arg_node<S: knuffel::traits::ErrorSpan, T: knuffel::traits::DecodeScalar<S>>(
name: &str,
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<T, DecodeError<S>> {
let mut iter_args = node.arguments.iter();
let val = iter_args.next().ok_or_else(|| {
DecodeError::missing(node, format!("additional argument `{name}` is required"))
})?;
let value = knuffel::traits::DecodeScalar::decode(val, ctx)?;
if let Some(val) = iter_args.next() {
ctx.emit_error(DecodeError::unexpected(
&val.literal,
"argument",
"unexpected argument",
));
}
for name in node.properties.keys() {
ctx.emit_error(DecodeError::unexpected(
name,
"property",
format!("unexpected property `{}`", name.escape_default()),
));
}
for child in node.children() {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
format!("unexpected node `{}`", child.node_name.escape_default()),
));
}
Ok(value)
}
2024-04-17 14:06:32 +04:00
impl<S> knuffel::Decode<S> for WorkspaceSwitchAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
2024-04-21 20:10:35 +04:00
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
2024-04-17 14:06:32 +04:00
}
}
impl<S> knuffel::Decode<S> for HorizontalViewMovementAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
2024-04-21 20:10:35 +04:00
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
2024-04-17 14:06:32 +04:00
}
}
impl<S> knuffel::Decode<S> for WindowMovementAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
2024-04-21 20:10:35 +04:00
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
2024-04-17 14:06:32 +04:00
}
}
impl<S> knuffel::Decode<S> for WindowOpenAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
2024-04-21 20:10:35 +04:00
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
2024-04-17 14:06:32 +04:00
}
}
impl<S> knuffel::Decode<S> for WindowCloseAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
2024-04-21 20:10:35 +04:00
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
2024-04-17 14:06:32 +04:00
}
}
impl<S> knuffel::Decode<S> for WindowResizeAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
2024-04-21 20:10:35 +04:00
let default = Self::default().anim;
let mut custom_shader = None;
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
if &**child.node_name == "custom-shader" {
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
Ok(true)
} else {
Ok(false)
}
})?;
Ok(Self {
anim,
custom_shader,
})
2024-04-17 14:06:32 +04:00
}
}
impl<S> knuffel::Decode<S> for ConfigNotificationOpenCloseAnim
2024-03-05 13:32:30 +04:00
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
2024-04-17 14:06:32 +04:00
let default = Self::default().0;
2024-04-21 20:10:35 +04:00
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
2024-04-17 14:06:32 +04:00
}
}
impl Animation {
fn decode_node<S: knuffel::traits::ErrorSpan>(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
default: Self,
2024-04-21 20:10:35 +04:00
mut process_children: impl FnMut(
&knuffel::ast::SpannedNode<S>,
&mut knuffel::decode::Context<S>,
) -> Result<bool, DecodeError<S>>,
2024-04-17 14:06:32 +04:00
) -> Result<Self, DecodeError<S>> {
#[derive(Default, PartialEq)]
struct OptionalEasingParams {
duration_ms: Option<u32>,
curve: Option<AnimationCurve>,
}
2024-03-05 13:32:30 +04:00
expect_only_children(node, ctx);
let mut off = false;
2024-04-17 14:06:32 +04:00
let mut easing_params = OptionalEasingParams::default();
2024-03-05 13:32:30 +04:00
let mut spring_params = None;
for child in node.children() {
match &**child.node_name {
"off" => {
knuffel::decode::check_flag_node(child, ctx);
if off {
ctx.emit_error(DecodeError::unexpected(
&child.node_name,
"node",
"duplicate node `off`, single node expected",
));
} else {
off = true;
}
}
"spring" => {
2024-04-17 14:06:32 +04:00
if easing_params != OptionalEasingParams::default() {
2024-03-05 13:32:30 +04:00
ctx.emit_error(DecodeError::unexpected(
child,
"node",
"cannot set both spring and easing parameters at once",
));
}
if spring_params.is_some() {
ctx.emit_error(DecodeError::unexpected(
&child.node_name,
"node",
"duplicate node `spring`, single node expected",
));
}
spring_params = Some(SpringParams::decode_node(child, ctx)?);
}
"duration-ms" => {
if spring_params.is_some() {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
"cannot set both spring and easing parameters at once",
));
}
if easing_params.duration_ms.is_some() {
ctx.emit_error(DecodeError::unexpected(
&child.node_name,
"node",
"duplicate node `duration-ms`, single node expected",
));
}
easing_params.duration_ms = Some(parse_arg_node("duration-ms", child, ctx)?);
}
"curve" => {
if spring_params.is_some() {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
"cannot set both spring and easing parameters at once",
));
}
if easing_params.curve.is_some() {
ctx.emit_error(DecodeError::unexpected(
&child.node_name,
"node",
"duplicate node `curve`, single node expected",
));
}
easing_params.curve = Some(parse_arg_node("curve", child, ctx)?);
}
name_str => {
2024-04-21 20:10:35 +04:00
if !process_children(child, ctx)? {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
format!("unexpected node `{}`", name_str.escape_default()),
));
}
2024-03-05 13:32:30 +04:00
}
}
}
let kind = if let Some(spring_params) = spring_params {
2024-04-17 14:06:32 +04:00
// Configured spring.
2024-03-05 13:32:30 +04:00
AnimationKind::Spring(spring_params)
2024-04-17 14:06:32 +04:00
} else if easing_params == OptionalEasingParams::default() {
// Did not configure anything.
default.kind
2024-03-05 13:32:30 +04:00
} else {
2024-04-17 14:06:32 +04:00
// Configured easing.
let default = if let AnimationKind::Easing(easing) = default.kind {
easing
} else {
// Generic fallback values for when the default animation is spring, but the user
// configured an easing animation.
EasingParams {
duration_ms: 250,
curve: AnimationCurve::EaseOutCubic,
}
};
AnimationKind::Easing(EasingParams {
duration_ms: easing_params.duration_ms.unwrap_or(default.duration_ms),
curve: easing_params.curve.unwrap_or(default.curve),
})
2024-03-05 13:32:30 +04:00
};
Ok(Self { off, kind })
}
}
impl<S> knuffel::Decode<S> for SpringParams
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
if let Some(type_name) = &node.type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
if let Some(val) = node.arguments.first() {
ctx.emit_error(DecodeError::unexpected(
&val.literal,
"argument",
"unexpected argument",
));
}
for child in node.children() {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
format!("unexpected node `{}`", child.node_name.escape_default()),
));
}
let mut damping_ratio = None;
let mut stiffness = None;
let mut epsilon = None;
for (name, val) in &node.properties {
match &***name {
"damping-ratio" => {
damping_ratio = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
}
"stiffness" => {
stiffness = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
}
"epsilon" => {
epsilon = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
}
name_str => {
ctx.emit_error(DecodeError::unexpected(
name,
"property",
format!("unexpected property `{}`", name_str.escape_default()),
));
}
}
}
let damping_ratio = damping_ratio
.ok_or_else(|| DecodeError::missing(node, "property `damping-ratio` is required"))?;
let stiffness = stiffness
.ok_or_else(|| DecodeError::missing(node, "property `stiffness` is required"))?;
let epsilon =
epsilon.ok_or_else(|| DecodeError::missing(node, "property `epsilon` is required"))?;
if !(0.1..=10.).contains(&damping_ratio) {
ctx.emit_error(DecodeError::conversion(
node,
"damping-ratio must be between 0.1 and 10.0",
));
}
if stiffness < 1 {
ctx.emit_error(DecodeError::conversion(node, "stiffness must be >= 1"));
}
if !(0.00001..=0.1).contains(&epsilon) {
ctx.emit_error(DecodeError::conversion(
node,
"epsilon must be between 0.00001 and 0.1",
));
}
Ok(SpringParams {
damping_ratio,
stiffness,
epsilon,
})
}
}
2024-05-01 19:06:08 +04:00
impl<S> knuffel::Decode<S> for CornerRadius
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
// Check for unexpected type name.
if let Some(type_name) = &node.type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
let decode_radius = |ctx: &mut knuffel::decode::Context<S>,
val: &knuffel::ast::Value<S>| {
// Check for unexpected type name.
if let Some(typ) = &val.type_name {
ctx.emit_error(DecodeError::TypeName {
span: typ.span().clone(),
found: Some((**typ).clone()),
expected: knuffel::errors::ExpectedType::no_type(),
rust_type: "str",
});
}
// Decode both integers and floats.
let radius = match *val.literal {
knuffel::ast::Literal::Int(ref x) => f32::from(match x.try_into() {
Ok(x) => x,
Err(err) => {
ctx.emit_error(DecodeError::conversion(&val.literal, err));
0i16
}
}),
knuffel::ast::Literal::Decimal(ref x) => match x.try_into() {
Ok(x) => x,
Err(err) => {
ctx.emit_error(DecodeError::conversion(&val.literal, err));
0.
}
},
_ => {
ctx.emit_error(DecodeError::scalar_kind(
knuffel::decode::Kind::Int,
&val.literal,
));
0.
}
};
if radius < 0. {
ctx.emit_error(DecodeError::conversion(&val.literal, "radius must be >= 0"));
}
radius
};
// Get the first argument.
let mut iter_args = node.arguments.iter();
let val = iter_args
.next()
.ok_or_else(|| DecodeError::missing(node, "additional argument is required"))?;
let top_left = decode_radius(ctx, val);
let mut rv = CornerRadius {
top_left,
top_right: top_left,
bottom_right: top_left,
bottom_left: top_left,
};
if let Some(val) = iter_args.next() {
rv.top_right = decode_radius(ctx, val);
let val = iter_args.next().ok_or_else(|| {
DecodeError::missing(node, "either 1 or 4 arguments are required")
})?;
rv.bottom_right = decode_radius(ctx, val);
let val = iter_args.next().ok_or_else(|| {
DecodeError::missing(node, "either 1 or 4 arguments are required")
})?;
rv.bottom_left = decode_radius(ctx, val);
// Check for unexpected following arguments.
if let Some(val) = iter_args.next() {
ctx.emit_error(DecodeError::unexpected(
&val.literal,
"argument",
"unexpected argument",
));
}
}
// Check for unexpected properties and children.
for name in node.properties.keys() {
ctx.emit_error(DecodeError::unexpected(
name,
"property",
format!("unexpected property `{}`", name.escape_default()),
));
}
for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
format!("unexpected node `{}`", child.node_name.escape_default()),
));
}
Ok(rv)
}
}
impl<S> knuffel::Decode<S> for Binds
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
expect_only_children(node, ctx);
let mut seen_keys = HashSet::new();
let mut binds = Vec::new();
for child in node.children() {
match Bind::decode_node(child, ctx) {
Err(e) => {
ctx.emit_error(e);
}
Ok(bind) => {
if seen_keys.insert(bind.key) {
binds.push(bind);
} else {
// ideally, this error should point to the previous instance of this keybind
//
// i (sodiboo) have tried to implement this in various ways:
// miette!(), #[derive(Diagnostic)]
// DecodeError::Custom, DecodeError::Conversion
// nothing seems to work, and i suspect it's not possible.
//
// DecodeError is fairly restrictive.
// even DecodeError::Custom just wraps a std::error::Error
// and this erases all rich information from miette. (why???)
//
// why does knuffel do this?
// from what i can tell, it doesn't even use DecodeError for much.
// it only ever converts them to a Report anyways!
// https://github.com/tailhook/knuffel/blob/c44c6b0c0f31ea6d1174d5d2ed41064922ea44ca/src/wrappers.rs#L55-L58
//
// besides like, allowing downstream users (such as us!)
// to match on parse failure, i don't understand why
// it doesn't just use a generic error type
//
// even the matching isn't consistent,
// because errors can also be omitted as ctx.emit_error.
// why does *that one* especially, require a DecodeError?
//
// anyways if you can make it format nicely, definitely do fix this
ctx.emit_error(DecodeError::unexpected(
&child.node_name,
"keybind",
"duplicate keybind",
));
}
}
}
}
Ok(Self(binds))
}
}
impl<S> knuffel::Decode<S> for Bind
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
2024-03-22 20:47:40 +04:00
if let Some(type_name) = &node.type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
for val in node.arguments.iter() {
ctx.emit_error(DecodeError::unexpected(
&val.literal,
"argument",
"no arguments expected for this node",
));
2024-03-22 20:47:40 +04:00
}
let key = node
.node_name
.parse::<Key>()
.map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?;
2024-03-22 20:47:40 +04:00
let mut cooldown = None;
let mut allow_when_locked = false;
let mut allow_when_locked_node = None;
2024-03-22 20:47:40 +04:00
for (name, val) in &node.properties {
match &***name {
"cooldown-ms" => {
cooldown = Some(Duration::from_millis(
knuffel::traits::DecodeScalar::decode(val, ctx)?,
));
}
"allow-when-locked" => {
allow_when_locked = knuffel::traits::DecodeScalar::decode(val, ctx)?;
allow_when_locked_node = Some(name);
}
2024-03-22 20:47:40 +04:00
name_str => {
ctx.emit_error(DecodeError::unexpected(
name,
"property",
format!("unexpected property `{}`", name_str.escape_default()),
));
}
}
}
let mut children = node.children();
// If the action is invalid but the key is fine, we still want to return something.
// That way, the parent can handle the existence of duplicate keybinds,
// even if their contents are not valid.
let dummy = Self {
key,
action: Action::Spawn(vec![]),
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
};
if let Some(child) = children.next() {
for unwanted_child in children {
ctx.emit_error(DecodeError::unexpected(
unwanted_child,
"node",
"only one action is allowed per keybind",
));
}
match Action::decode_node(child, ctx) {
Ok(action) => {
if !matches!(action, Action::Spawn(_)) {
if let Some(node) = allow_when_locked_node {
ctx.emit_error(DecodeError::unexpected(
node,
"property",
"allow-when-locked can only be set on spawn binds",
));
}
}
Ok(Self {
key,
action,
cooldown,
allow_when_locked,
})
}
Err(e) => {
ctx.emit_error(e);
Ok(dummy)
}
}
} else {
ctx.emit_error(DecodeError::missing(
node,
"expected an action for this keybind",
));
Ok(dummy)
}
}
}
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 if part.eq_ignore_ascii_case("iso_level3_shift")
|| part.eq_ignore_ascii_case("mod5")
{
modifiers |= Modifiers::ISO_LEVEL3_SHIFT;
2023-09-05 12:58:51 +04:00
} else {
return Err(miette!("invalid modifier: {part}"));
}
}
let trigger = if key.eq_ignore_ascii_case("WheelScrollDown") {
Trigger::WheelScrollDown
} else if key.eq_ignore_ascii_case("WheelScrollUp") {
Trigger::WheelScrollUp
} else if key.eq_ignore_ascii_case("WheelScrollLeft") {
Trigger::WheelScrollLeft
} else if key.eq_ignore_ascii_case("WheelScrollRight") {
Trigger::WheelScrollRight
2024-03-23 20:23:21 +04:00
} else if key.eq_ignore_ascii_case("TouchpadScrollDown") {
Trigger::TouchpadScrollDown
} else if key.eq_ignore_ascii_case("TouchpadScrollUp") {
Trigger::TouchpadScrollUp
} else if key.eq_ignore_ascii_case("TouchpadScrollLeft") {
Trigger::TouchpadScrollLeft
} else if key.eq_ignore_ascii_case("TouchpadScrollRight") {
Trigger::TouchpadScrollRight
2024-03-22 10:36:19 +04:00
} else {
let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
if keysym.raw() == KEY_NoSymbol {
return Err(miette!("invalid key: {key}"));
}
Trigger::Keysym(keysym)
};
2023-09-05 12:58:51 +04:00
2024-03-22 10:36:19 +04:00
Ok(Key { trigger, modifiers })
2023-09-05 12:58:51 +04:00
}
}
2024-03-13 21:26:03 -07:00
impl FromStr for ClickMethod {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"clickfinger" => Ok(Self::Clickfinger),
"button-areas" => Ok(Self::ButtonAreas),
_ => Err(miette!(
r#"invalid click method, can be "button-areas" or "clickfinger""#
)),
}
}
}
2024-01-08 10:15:29 +04:00
impl FromStr for AccelProfile {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"adaptive" => Ok(Self::Adaptive),
"flat" => Ok(Self::Flat),
_ => Err(miette!(
r#"invalid accel profile, can be "adaptive" or "flat""#
)),
}
}
}
2024-01-08 10:32:04 +04:00
impl FromStr for TapButtonMap {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"left-right-middle" => Ok(Self::LeftRightMiddle),
"left-middle-right" => Ok(Self::LeftMiddleRight),
_ => Err(miette!(
r#"invalid tap button map, can be "left-right-middle" or "left-middle-right""#
)),
}
}
}
2024-01-07 09:07:22 +04:00
pub fn set_miette_hook() -> Result<(), miette::InstallError> {
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new())))
}
2023-09-05 12:58:51 +04:00
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn check(text: &str, expected: Config) {
2024-02-13 12:16:58 +04:00
let _ = set_miette_hook();
2023-10-03 07:41:05 +04:00
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(
2024-02-21 21:27:44 +04:00
r##"
2023-09-05 12:58:51 +04:00
input {
keyboard {
2023-09-16 20:01:52 +04:00
repeat-delay 600
repeat-rate 25
track-layout "window"
2023-09-05 12:58:51 +04:00
xkb {
layout "us,ru"
options "grp:win_space_toggle"
}
}
touchpad {
tap
2024-01-08 10:24:00 +04:00
dwt
2024-02-03 08:20:15 +04:00
dwtp
2024-03-13 21:26:03 -07:00
click-method "clickfinger"
2023-09-05 12:58:51 +04:00
accel-speed 0.2
2024-01-08 10:15:29 +04:00
accel-profile "flat"
2024-01-08 10:32:04 +04:00
tap-button-map "left-middle-right"
2023-09-05 12:58:51 +04:00
}
2023-10-03 17:02:07 +04:00
2024-01-08 11:53:34 +04:00
mouse {
natural-scroll
accel-speed 0.4
accel-profile "flat"
}
trackpoint {
natural-scroll
accel-speed 0.0
accel-profile "flat"
}
2023-10-03 17:02:07 +04:00
tablet {
map-to-output "eDP-1"
}
2023-12-28 09:36:10 +04:00
2024-02-24 18:32:13 +01:00
touch {
map-to-output "eDP-1"
}
2023-12-28 09:36:10 +04:00
disable-power-key-handling
2024-02-26 18:47:46 +01:00
warp-mouse-to-focus
2024-03-18 18:17:04 +04:00
focus-follows-mouse
2024-03-19 14:27:52 +00:00
workspace-auto-back-and-forth
2023-09-05 12:58:51 +04:00
}
output "eDP-1" {
scale 2.0
2024-01-28 14:25:40 +01:00
transform "flipped-90"
2023-09-30 11:33:02 +04:00
position x=10 y=20
2023-10-03 08:35:24 +04:00
mode "1920x1080@144"
2024-04-14 09:23:15 +04:00
variable-refresh-rate
}
layout {
focus-ring {
width 5
active-color 0 100 200 255
inactive-color 255 200 100 0
2024-02-21 21:27:44 +04:00
active-gradient from="rgba(10, 20, 30, 1.0)" to="#0080ffff" relative-to="workspace-view"
}
2023-09-21 19:58:03 +04:00
border {
width 3
2024-02-26 09:02:47 +04:00
inactive-color "rgba(255, 200, 100, 0.0)"
}
2023-09-26 13:09:33 +04:00
preset-column-widths {
proportion 0.25
proportion 0.5
fixed 960
fixed 1280
}
default-column-width { proportion 0.25; }
2023-09-26 13:44:37 +04:00
gaps 8
2023-10-01 17:42:56 +04:00
struts {
left 1
right 2
top 3
}
2024-01-08 17:17:19 +04:00
center-focused-column "on-overflow"
}
spawn-at-startup "alacritty" "-e" "fish"
2023-11-02 21:07:29 +04:00
prefer-no-csd
2023-10-07 17:45:55 +04:00
cursor {
xcursor-theme "breeze_cursors"
xcursor-size 16
2023-12-21 08:37:30 +04:00
}
2023-10-31 14:23:54 +04:00
screenshot-path "~/Screenshots/screenshot.png"
2023-10-31 08:57:44 +04:00
hotkey-overlay {
skip-at-startup
}
2024-02-07 17:05:15 +04:00
animations {
slowdown 2.0
2024-03-05 13:32:30 +04:00
workspace-switch {
spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001
}
2024-02-07 17:05:15 +04:00
horizontal-view-movement {
duration-ms 100
curve "ease-out-expo"
}
2024-03-05 13:32:30 +04:00
window-open { off; }
2024-02-07 17:05:15 +04:00
}
2024-02-24 10:08:56 +04:00
environment {
QT_QPA_PLATFORM "wayland"
DISPLAY null
}
2024-02-13 17:46:37 +04:00
window-rule {
match app-id=".*alacritty"
exclude title="~"
2024-03-23 16:16:52 +04:00
exclude is-active=true is-focused=false
2024-02-13 17:46:37 +04:00
open-on-output "eDP-1"
2024-02-23 14:24:39 +04:00
open-maximized true
2024-02-24 08:44:21 +04:00
open-fullscreen false
2024-04-24 21:49:07 +04:00
2024-04-24 22:17:53 +04:00
focus-ring {
off
width 3
}
2024-04-24 21:49:07 +04:00
border {
on
width 8
}
2024-02-13 17:46:37 +04:00
}
2023-09-05 12:58:51 +04:00
binds {
Mod+T allow-when-locked=true { spawn "alacritty"; }
2023-09-05 12:58:51 +04:00
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; }
2024-02-12 07:48:31 +04:00
Mod+1 { focus-workspace 1; }
Mod+Shift+E { quit skip-confirmation=true; }
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
2023-09-05 12:58:51 +04:00
}
2023-09-06 15:49:46 +04:00
debug {
2024-01-06 09:14:48 +04:00
render-drm-device "/dev/dri/renderD129"
2023-09-06 15:49:46 +04:00
}
2024-02-21 21:27:44 +04:00
"##,
2023-09-05 12:58:51 +04:00
Config {
input: Input {
keyboard: Keyboard {
xkb: Xkb {
2024-03-23 10:10:01 +04:00
layout: "us,ru".to_owned(),
2023-09-05 12:58:51 +04:00
options: Some("grp:win_space_toggle".to_owned()),
..Default::default()
},
2023-09-16 20:01:52 +04:00
repeat_delay: 600,
repeat_rate: 25,
track_layout: TrackLayout::Window,
2023-09-05 12:58:51 +04:00
},
touchpad: Touchpad {
tap: true,
2024-01-08 10:24:00 +04:00
dwt: true,
2024-02-03 08:20:15 +04:00
dwtp: true,
2024-03-13 21:26:03 -07:00
click_method: Some(ClickMethod::Clickfinger),
2023-09-05 12:58:51 +04:00
natural_scroll: false,
accel_speed: 0.2,
2024-01-08 10:15:29 +04:00
accel_profile: Some(AccelProfile::Flat),
2024-01-08 10:32:04 +04:00
tap_button_map: Some(TapButtonMap::LeftMiddleRight),
2023-09-05 12:58:51 +04:00
},
2024-01-08 11:53:34 +04:00
mouse: Mouse {
natural_scroll: true,
accel_speed: 0.4,
accel_profile: Some(AccelProfile::Flat),
},
trackpoint: Trackpoint {
natural_scroll: true,
accel_speed: 0.0,
accel_profile: Some(AccelProfile::Flat),
},
2023-10-03 17:02:07 +04:00
tablet: Tablet {
map_to_output: Some("eDP-1".to_owned()),
},
2024-02-24 18:32:13 +01:00
touch: Touch {
map_to_output: Some("eDP-1".to_owned()),
},
2023-12-28 09:36:10 +04:00
disable_power_key_handling: true,
2024-02-26 18:47:46 +01:00
warp_mouse_to_focus: true,
2024-03-18 18:17:04 +04:00
focus_follows_mouse: true,
2024-03-19 14:27:52 +00:00
workspace_auto_back_and_forth: true,
2023-09-05 12:58:51 +04:00
},
outputs: vec![Output {
2023-12-18 10:27:41 +04:00
off: false,
name: "eDP-1".to_owned(),
scale: 2.,
2024-01-28 14:25:40 +01:00
transform: Transform::Flipped90,
2023-09-30 11:33:02 +04:00
position: Some(Position { x: 10, y: 20 }),
2024-05-05 10:19:47 +04:00
mode: Some(ConfiguredMode {
2023-10-03 08:35:24 +04:00
width: 1920,
height: 1080,
refresh: Some(144.),
}),
2024-04-14 09:23:15 +04:00
variable_refresh_rate: true,
}],
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,
},
2024-02-21 21:27:44 +04:00
active_gradient: Some(Gradient {
from: Color::new(10, 20, 30, 255),
to: Color::new(0, 128, 255, 255),
angle: 180,
relative_to: GradientRelativeTo::WorkspaceView,
}),
inactive_gradient: None,
2023-09-26 13:09:33 +04:00
},
border: Border {
off: false,
width: 3,
active_color: Color {
r: 255,
g: 200,
b: 127,
a: 255,
},
inactive_color: Color {
r: 255,
g: 200,
b: 100,
a: 0,
},
2024-02-21 21:27:44 +04:00
active_gradient: None,
inactive_gradient: None,
},
preset_column_widths: vec![
PresetWidth::Proportion(0.25),
PresetWidth::Proportion(0.5),
PresetWidth::Fixed(960),
PresetWidth::Fixed(1280),
],
default_column_width: Some(DefaultColumnWidth(Some(PresetWidth::Proportion(
0.25,
)))),
gaps: 8,
struts: Struts {
left: 1,
right: 2,
top: 3,
bottom: 0,
},
2024-01-08 17:17:19 +04:00
center_focused_column: CenterFocusedColumn::OnOverflow,
},
spawn_at_startup: vec![SpawnAtStartup {
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
}],
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-31 14:23:54 +04:00
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
hotkey_overlay: HotkeyOverlay {
skip_at_startup: true,
},
2024-02-07 17:05:15 +04:00
animations: Animations {
slowdown: 2.,
2024-04-17 14:06:32 +04:00
workspace_switch: WorkspaceSwitchAnim(Animation {
2024-03-05 13:32:30 +04:00
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 1000,
epsilon: 0.0001,
}),
2024-04-17 14:06:32 +04:00
}),
horizontal_view_movement: HorizontalViewMovementAnim(Animation {
2024-03-05 13:32:30 +04:00
off: false,
kind: AnimationKind::Easing(EasingParams {
2024-04-17 14:06:32 +04:00
duration_ms: 100,
curve: AnimationCurve::EaseOutExpo,
2024-03-05 13:32:30 +04:00
}),
2024-04-17 14:06:32 +04:00
}),
window_open: WindowOpenAnim(Animation {
2024-03-05 13:32:30 +04:00
off: true,
2024-04-17 14:06:32 +04:00
..WindowOpenAnim::default().0
}),
2024-02-07 17:05:15 +04:00
..Default::default()
},
2024-02-24 10:08:56 +04:00
environment: Environment(vec![
EnvironmentVariable {
name: String::from("QT_QPA_PLATFORM"),
value: Some(String::from("wayland")),
},
EnvironmentVariable {
name: String::from("DISPLAY"),
value: None,
},
]),
2024-02-13 17:46:37 +04:00
window_rules: vec![WindowRule {
matches: vec![Match {
app_id: Some(Regex::new(".*alacritty").unwrap()),
title: None,
2024-03-23 14:38:07 +04:00
is_active: None,
2024-03-23 16:16:52 +04:00
is_focused: None,
2024-02-13 17:46:37 +04:00
}],
2024-03-23 14:38:07 +04:00
excludes: vec![
Match {
app_id: None,
title: Some(Regex::new("~").unwrap()),
is_active: None,
2024-03-23 16:16:52 +04:00
is_focused: None,
2024-03-23 14:38:07 +04:00
},
Match {
app_id: None,
title: None,
is_active: Some(true),
2024-03-23 16:16:52 +04:00
is_focused: Some(false),
2024-03-23 14:38:07 +04:00
},
],
2024-02-13 17:46:37 +04:00
open_on_output: Some("eDP-1".to_owned()),
2024-02-23 14:24:39 +04:00
open_maximized: Some(true),
2024-02-24 08:44:21 +04:00
open_fullscreen: Some(false),
2024-04-24 22:17:53 +04:00
focus_ring: BorderRule {
off: true,
width: Some(3),
..Default::default()
},
2024-04-24 21:49:07 +04:00
border: BorderRule {
on: true,
width: Some(8),
..Default::default()
},
2024-02-13 17:46:37 +04:00
..Default::default()
}],
2023-09-05 12:58:51 +04:00
binds: Binds(vec![
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::t),
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
action: Action::Spawn(vec!["alacritty".to_owned()]),
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: true,
2023-09-05 12:58:51 +04:00
},
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::q),
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
action: Action::CloseWindow,
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
2023-09-05 12:58:51 +04:00
},
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::h),
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
action: Action::FocusMonitorLeft,
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
2023-09-05 12:58:51 +04:00
},
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::l),
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
},
action: Action::MoveWindowToMonitorRight,
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
2023-09-05 12:58:51 +04:00
},
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::comma),
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
action: Action::ConsumeWindowIntoColumn,
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
2023-09-05 12:58:51 +04:00
},
2023-09-16 12:14:02 +04:00
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::_1),
2023-09-16 12:14:02 +04:00
modifiers: Modifiers::COMPOSITOR,
},
action: Action::FocusWorkspace(1),
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
2023-09-16 12:14:02 +04:00
},
Bind {
key: Key {
2024-03-22 10:36:19 +04:00
trigger: Trigger::Keysym(Keysym::e),
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
action: Action::Quit(true),
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
},
2024-03-22 10:36:19 +04:00
Bind {
key: Key {
trigger: Trigger::WheelScrollDown,
2024-03-22 10:36:19 +04:00
modifiers: Modifiers::COMPOSITOR,
},
action: Action::FocusWorkspaceDown,
2024-03-22 20:47:40 +04:00
cooldown: Some(Duration::from_millis(150)),
allow_when_locked: false,
2024-03-22 10:36:19 +04:00
},
2023-09-05 12:58:51 +04:00
]),
2023-09-06 15:49:46 +04:00
debug: DebugConfig {
2024-01-06 09:14:48 +04:00
render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")),
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!(
2024-05-05 10:19:47 +04:00
"2560x1600@165.004".parse::<ConfiguredMode>().unwrap(),
ConfiguredMode {
2023-10-03 08:35:24 +04:00
width: 2560,
height: 1600,
refresh: Some(165.004),
},
);
assert_eq!(
2024-05-05 10:19:47 +04:00
"1920x1080".parse::<ConfiguredMode>().unwrap(),
ConfiguredMode {
2023-10-03 08:35:24 +04:00
width: 1920,
height: 1080,
refresh: None,
},
);
2024-05-05 10:19:47 +04:00
assert!("1920".parse::<ConfiguredMode>().is_err());
assert!("1920x".parse::<ConfiguredMode>().is_err());
assert!("1920x1080@".parse::<ConfiguredMode>().is_err());
assert!("1920x1080@60Hz".parse::<ConfiguredMode>().is_err());
2023-10-03 08:35:24 +04:00
}
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());
}
#[test]
fn parse_iso_level3_shift() {
assert_eq!(
"ISO_Level3_Shift+A".parse::<Key>().unwrap(),
Key {
trigger: Trigger::Keysym(Keysym::a),
modifiers: Modifiers::ISO_LEVEL3_SHIFT
},
);
assert_eq!(
"Mod5+A".parse::<Key>().unwrap(),
Key {
trigger: Trigger::Keysym(Keysym::a),
modifiers: Modifiers::ISO_LEVEL3_SHIFT
},
);
}
2023-09-05 12:58:51 +04:00
}