2024-01-07 09:07:22 +04:00
|
|
|
#[macro_use]
|
|
|
|
|
extern crate tracing;
|
|
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
use std::ffi::OsStr;
|
2024-01-18 11:02:15 +04:00
|
|
|
use std::path::{Path, PathBuf};
|
2023-09-05 12:58:51 +04:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
|
|
use bitflags::bitflags;
|
2024-03-01 12:50:49 +01:00
|
|
|
use knuffel::errors::DecodeError;
|
2024-01-07 09:07:22 +04:00
|
|
|
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
|
2024-02-10 09:33:32 +04:00
|
|
|
use niri_ipc::{LayoutSwitchTarget, SizeChange};
|
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,
|
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)]
|
2024-01-06 13:04:21 +04:00
|
|
|
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)]
|
2024-01-20 08:31:05 +04:00
|
|
|
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)]
|
2024-02-14 16:24:46 +01:00
|
|
|
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)]
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
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)]
|
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>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-14 16:24:46 +01:00
|
|
|
#[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>,
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
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))]
|
|
|
|
|
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.,
|
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,
|
2023-09-21 13:48:32 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-28 14:25:40 +01:00
|
|
|
/// Output transform, which goes counter-clockwise.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
pub enum Transform {
|
|
|
|
|
Normal,
|
|
|
|
|
_90,
|
|
|
|
|
_180,
|
|
|
|
|
_270,
|
|
|
|
|
Flipped,
|
|
|
|
|
Flipped90,
|
|
|
|
|
Flipped180,
|
|
|
|
|
Flipped270,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Transform {
|
|
|
|
|
type Err = miette::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
match s {
|
|
|
|
|
"normal" => Ok(Self::Normal),
|
|
|
|
|
"90" => Ok(Self::_90),
|
|
|
|
|
"180" => Ok(Self::_180),
|
|
|
|
|
"270" => Ok(Self::_270),
|
|
|
|
|
"flipped" => Ok(Self::Flipped),
|
|
|
|
|
"flipped-90" => Ok(Self::Flipped90),
|
|
|
|
|
"flipped-180" => Ok(Self::Flipped180),
|
|
|
|
|
"flipped-270" => Ok(Self::Flipped270),
|
|
|
|
|
_ => Err(miette!(concat!(
|
|
|
|
|
r#"invalid transform, can be "90", "180", "270", "#,
|
|
|
|
|
r#""flipped", "flipped-90", "flipped-180" or "flipped-270""#
|
|
|
|
|
))),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Transform> for smithay::utils::Transform {
|
|
|
|
|
fn from(value: Transform) -> Self {
|
|
|
|
|
match value {
|
|
|
|
|
Transform::Normal => Self::Normal,
|
|
|
|
|
Transform::_90 => Self::_90,
|
|
|
|
|
Transform::_180 => Self::_180,
|
|
|
|
|
Transform::_270 => Self::_270,
|
|
|
|
|
Transform::Flipped => Self::Flipped,
|
|
|
|
|
Transform::Flipped90 => Self::Flipped90,
|
|
|
|
|
Transform::Flipped180 => Self::Flipped180,
|
|
|
|
|
Transform::Flipped270 => Self::Flipped270,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-16 08:43:28 +04:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
2023-10-03 08:35:24 +04:00
|
|
|
pub struct Mode {
|
|
|
|
|
pub width: u16,
|
|
|
|
|
pub height: u16,
|
|
|
|
|
pub refresh: Option<f64>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 13:14:49 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
2024-01-06 13:04:21 +04:00
|
|
|
pub struct Layout {
|
|
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub focus_ring: FocusRing,
|
2024-02-12 09:34:54 +04:00
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub border: Border,
|
2024-01-06 13:04:21 +04:00
|
|
|
#[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,
|
2024-02-13 13:14:49 +04:00
|
|
|
#[knuffel(child, unwrap(argument), default = Self::default().gaps)]
|
2024-01-06 13:04:21 +04:00
|
|
|
pub gaps: u16,
|
|
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
pub struts: Struts,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 13:14:49 +04:00
|
|
|
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>,
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2024-02-12 09:34:54 +04:00
|
|
|
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub width: u16,
|
2024-02-12 09:34:54 +04:00
|
|
|
#[knuffel(child, default = Self::default().active_color)]
|
2023-09-26 13:09:33 +04:00
|
|
|
pub active_color: Color,
|
2024-02-12 09:34:54 +04:00
|
|
|
#[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,
|
2023-10-31 09:07:11 +04:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 09:34:54 +04:00
|
|
|
#[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>,
|
2024-02-12 09:34:54 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2023-12-27 21:51:42 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 09:34:54 +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-02-12 09:34:54 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
2023-10-31 09:07:11 +04:00
|
|
|
pub r: u8,
|
|
|
|
|
pub g: u8,
|
|
|
|
|
pub b: u8,
|
|
|
|
|
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 {
|
2024-02-01 18:53:45 +04:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-20 08:31:05 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
pub struct HotkeyOverlay {
|
|
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub skip_at_startup: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-07 17:05:15 +04:00
|
|
|
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub struct Animations {
|
|
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub off: bool,
|
|
|
|
|
#[knuffel(child, unwrap(argument), default = 1.)]
|
|
|
|
|
pub slowdown: f64,
|
|
|
|
|
#[knuffel(child, default = Animation::default_workspace_switch())]
|
|
|
|
|
pub workspace_switch: Animation,
|
|
|
|
|
#[knuffel(child, default = Animation::default_horizontal_view_movement())]
|
|
|
|
|
pub horizontal_view_movement: Animation,
|
|
|
|
|
#[knuffel(child, default = Animation::default_window_open())]
|
|
|
|
|
pub window_open: Animation,
|
|
|
|
|
#[knuffel(child, default = Animation::default_config_notification_open_close())]
|
|
|
|
|
pub config_notification_open_close: Animation,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Animations {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
off: false,
|
|
|
|
|
slowdown: 1.,
|
|
|
|
|
workspace_switch: Animation::default_workspace_switch(),
|
|
|
|
|
horizontal_view_movement: Animation::default_horizontal_view_movement(),
|
|
|
|
|
window_open: Animation::default_window_open(),
|
|
|
|
|
config_notification_open_close: Animation::default_config_notification_open_close(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 13:32:30 +04:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
2024-02-07 17:05:15 +04:00
|
|
|
pub struct Animation {
|
|
|
|
|
pub off: bool,
|
2024-03-05 13:32:30 +04:00
|
|
|
pub kind: AnimationKind,
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Animation {
|
|
|
|
|
pub const fn unfilled() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
off: false,
|
2024-03-05 13:32:30 +04:00
|
|
|
kind: AnimationKind::Easing(EasingParams::unfilled()),
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
off: false,
|
2024-03-05 13:32:30 +04:00
|
|
|
kind: AnimationKind::Easing(EasingParams::default()),
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn default_workspace_switch() -> Self {
|
2024-03-05 13:32:30 +04:00
|
|
|
Self {
|
|
|
|
|
off: false,
|
|
|
|
|
kind: AnimationKind::Spring(SpringParams {
|
|
|
|
|
damping_ratio: 1.,
|
|
|
|
|
stiffness: 1000,
|
|
|
|
|
epsilon: 0.0001,
|
|
|
|
|
}),
|
|
|
|
|
}
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn default_horizontal_view_movement() -> Self {
|
2024-03-05 13:32:30 +04:00
|
|
|
Self {
|
|
|
|
|
off: false,
|
|
|
|
|
kind: AnimationKind::Spring(SpringParams {
|
|
|
|
|
damping_ratio: 1.,
|
|
|
|
|
stiffness: 800,
|
|
|
|
|
epsilon: 0.0001,
|
|
|
|
|
}),
|
|
|
|
|
}
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn default_config_notification_open_close() -> Self {
|
2024-03-05 13:32:30 +04:00
|
|
|
Self {
|
|
|
|
|
off: false,
|
|
|
|
|
kind: AnimationKind::Spring(SpringParams {
|
|
|
|
|
damping_ratio: 0.6,
|
|
|
|
|
stiffness: 1000,
|
|
|
|
|
epsilon: 0.001,
|
|
|
|
|
}),
|
|
|
|
|
}
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn default_window_open() -> Self {
|
|
|
|
|
Self {
|
2024-03-05 13:32:30 +04:00
|
|
|
off: false,
|
|
|
|
|
kind: AnimationKind::Easing(EasingParams {
|
|
|
|
|
duration_ms: Some(150),
|
|
|
|
|
curve: Some(AnimationCurve::EaseOutExpo),
|
|
|
|
|
}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub enum AnimationKind {
|
|
|
|
|
Easing(EasingParams),
|
|
|
|
|
Spring(SpringParams),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub struct EasingParams {
|
|
|
|
|
pub duration_ms: Option<u32>,
|
|
|
|
|
pub curve: Option<AnimationCurve>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EasingParams {
|
|
|
|
|
pub const fn unfilled() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
duration_ms: None,
|
|
|
|
|
curve: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
duration_ms: Some(250),
|
|
|
|
|
curve: Some(AnimationCurve::EaseOutCubic),
|
2024-02-07 17:05:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub enum AnimationCurve {
|
|
|
|
|
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>,
|
|
|
|
|
|
|
|
|
|
#[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>,
|
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>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq for Match {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.app_id.as_ref().map(Regex::as_str) == other.app_id.as_ref().map(Regex::as_str)
|
|
|
|
|
&& self.title.as_ref().map(Regex::as_str) == other.title.as_ref().map(Regex::as_str)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
#[derive(Debug, Default, PartialEq)]
|
|
|
|
|
pub struct Binds(pub Vec<Bind>);
|
2023-09-05 12:58:51 +04:00
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2023-09-05 12:58:51 +04:00
|
|
|
pub struct Bind {
|
|
|
|
|
pub key: Key,
|
2024-03-01 12:50:49 +01:00
|
|
|
pub action: Action,
|
2023-09-05 12:58:51 +04:00
|
|
|
}
|
|
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
2023-09-05 12:58:51 +04:00
|
|
|
pub struct Key {
|
|
|
|
|
pub keysym: Keysym,
|
|
|
|
|
pub modifiers: Modifiers,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bitflags! {
|
2024-03-01 12:50:49 +01:00
|
|
|
#[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 COMPOSITOR = 16;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
2024-02-12 07:53:06 +04:00
|
|
|
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,
|
|
|
|
|
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,
|
2024-02-03 20:25:08 +01:00
|
|
|
ConsumeOrExpelWindowLeft,
|
|
|
|
|
ConsumeOrExpelWindowRight,
|
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),
|
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,
|
2023-12-02 23:57:01 +01:00
|
|
|
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 {
|
2024-02-12 07:53:06 +04:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
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 {
|
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,
|
2024-01-06 09:14:48 +04:00
|
|
|
#[knuffel(child, unwrap(argument))]
|
|
|
|
|
pub render_drm_device: Option<PathBuf>,
|
2024-03-01 07:49:10 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub emulate_zero_presentation_time: bool,
|
2023-09-06 15:49:46 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-05 12:58:51 +04:00
|
|
|
impl Config {
|
2024-01-18 11:02:15 +04:00
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 11:02:15 +04:00
|
|
|
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:?}"))?;
|
|
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
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:?}");
|
2024-01-18 11:02:15 +04:00
|
|
|
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-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>,
|
2024-03-01 12:50:49 +01:00
|
|
|
) -> 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 {
|
2024-03-01 12:50:49 +01:00
|
|
|
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();
|
2024-03-01 12:50:49 +01:00
|
|
|
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 {
|
2024-03-01 12:50:49 +01:00
|
|
|
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.
|
2024-03-01 12:50:49 +01:00
|
|
|
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() {
|
2024-03-01 12:50:49 +01:00
|
|
|
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() {
|
2024-03-01 12:50:49 +01:00
|
|
|
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(&[]) {
|
2024-03-01 12:50:49 +01:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-01 12:50:49 +01:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<S> knuffel::Decode<S> for Animation
|
|
|
|
|
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 off = false;
|
|
|
|
|
let mut easing_params = EasingParams::unfilled();
|
|
|
|
|
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" => {
|
|
|
|
|
if easing_params != EasingParams::unfilled() {
|
|
|
|
|
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 => {
|
|
|
|
|
ctx.emit_error(DecodeError::unexpected(
|
|
|
|
|
child,
|
|
|
|
|
"node",
|
|
|
|
|
format!("unexpected node `{}`", name_str.escape_default()),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let kind = if let Some(spring_params) = spring_params {
|
|
|
|
|
AnimationKind::Spring(spring_params)
|
|
|
|
|
} else {
|
|
|
|
|
AnimationKind::Easing(easing_params)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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-03-01 12:50:49 +01:00
|
|
|
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>> {
|
|
|
|
|
expect_only_children(node, ctx);
|
|
|
|
|
|
|
|
|
|
let key = node
|
|
|
|
|
.node_name
|
|
|
|
|
.parse::<Key>()
|
|
|
|
|
.map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?;
|
|
|
|
|
|
|
|
|
|
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![]),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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) => Ok(Self { key, action }),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
ctx.emit_error(e);
|
|
|
|
|
Ok(dummy)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ctx.emit_error(DecodeError::missing(
|
|
|
|
|
node,
|
|
|
|
|
"expected an action for this keybind",
|
|
|
|
|
));
|
|
|
|
|
Ok(dummy)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
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
|
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"
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-14 16:24:46 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +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"
|
2023-09-21 13:48:32 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
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"
|
2024-01-06 13:04:21 +04:00
|
|
|
}
|
2023-09-21 19:58:03 +04:00
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
border {
|
|
|
|
|
width 3
|
2024-02-26 09:02:47 +04:00
|
|
|
inactive-color "rgba(255, 200, 100, 0.0)"
|
2024-01-06 13:04:21 +04:00
|
|
|
}
|
2023-09-26 13:09:33 +04:00
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
preset-column-widths {
|
|
|
|
|
proportion 0.25
|
|
|
|
|
proportion 0.5
|
|
|
|
|
fixed 960
|
|
|
|
|
fixed 1280
|
|
|
|
|
}
|
2023-12-27 21:51:42 +04:00
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
default-column-width { proportion 0.25; }
|
2023-09-26 13:44:37 +04:00
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
gaps 8
|
2023-10-01 17:42:56 +04:00
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
struts {
|
|
|
|
|
left 1
|
|
|
|
|
right 2
|
|
|
|
|
top 3
|
|
|
|
|
}
|
2024-01-08 17:17:19 +04:00
|
|
|
|
|
|
|
|
center-focused-column "on-overflow"
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
spawn-at-startup "alacritty" "-e" "fish"
|
2023-11-02 21:07:29 +04:00
|
|
|
|
2024-01-06 13:04:21 +04:00
|
|
|
prefer-no-csd
|
2023-10-07 17:45:55 +04:00
|
|
|
|
2024-01-06 13:04:21 +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
|
|
|
|
2024-01-20 08:31:05 +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="~"
|
|
|
|
|
|
|
|
|
|
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-02-13 17:46:37 +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; }
|
2024-02-12 07:48:31 +04:00
|
|
|
Mod+1 { focus-workspace 1; }
|
2024-02-12 07:53:06 +04:00
|
|
|
Mod+Shift+E { quit skip-confirmation=true; }
|
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 {
|
|
|
|
|
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,
|
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),
|
|
|
|
|
},
|
2024-02-14 16:24:46 +01:00
|
|
|
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
|
|
|
},
|
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.,
|
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 }),
|
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
|
|
|
}],
|
2024-01-06 13:04:21 +04:00
|
|
|
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
|
|
|
},
|
2024-02-12 09:34:54 +04:00
|
|
|
border: Border {
|
2024-01-06 13:04:21 +04:00
|
|
|
off: false,
|
|
|
|
|
width: 3,
|
|
|
|
|
active_color: Color {
|
2024-02-12 09:34:54 +04:00
|
|
|
r: 255,
|
|
|
|
|
g: 200,
|
|
|
|
|
b: 127,
|
2024-01-06 13:04:21 +04:00
|
|
|
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,
|
2023-12-27 21:51:42 +04:00
|
|
|
},
|
2024-01-06 13:04:21 +04:00
|
|
|
preset_column_widths: vec![
|
|
|
|
|
PresetWidth::Proportion(0.25),
|
|
|
|
|
PresetWidth::Proportion(0.5),
|
|
|
|
|
PresetWidth::Fixed(960),
|
|
|
|
|
PresetWidth::Fixed(1280),
|
|
|
|
|
],
|
2024-03-01 12:50:49 +01:00
|
|
|
default_column_width: Some(DefaultColumnWidth(Some(PresetWidth::Proportion(
|
2024-01-06 13:04:21 +04:00
|
|
|
0.25,
|
2024-03-01 12:50:49 +01:00
|
|
|
)))),
|
2024-01-06 13:04:21 +04:00
|
|
|
gaps: 8,
|
|
|
|
|
struts: Struts {
|
|
|
|
|
left: 1,
|
|
|
|
|
right: 2,
|
|
|
|
|
top: 3,
|
|
|
|
|
bottom: 0,
|
2023-12-27 21:51:42 +04:00
|
|
|
},
|
2024-01-08 17:17:19 +04:00
|
|
|
center_focused_column: CenterFocusedColumn::OnOverflow,
|
2023-12-27 21:51:42 +04:00
|
|
|
},
|
2024-01-06 13:04:21 +04:00
|
|
|
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")),
|
2024-01-20 08:31:05 +04:00
|
|
|
hotkey_overlay: HotkeyOverlay {
|
|
|
|
|
skip_at_startup: true,
|
|
|
|
|
},
|
2024-02-07 17:05:15 +04:00
|
|
|
animations: Animations {
|
|
|
|
|
slowdown: 2.,
|
|
|
|
|
workspace_switch: Animation {
|
2024-03-05 13:32:30 +04:00
|
|
|
off: false,
|
|
|
|
|
kind: AnimationKind::Spring(SpringParams {
|
|
|
|
|
damping_ratio: 1.,
|
|
|
|
|
stiffness: 1000,
|
|
|
|
|
epsilon: 0.0001,
|
|
|
|
|
}),
|
2024-02-07 17:05:15 +04:00
|
|
|
},
|
|
|
|
|
horizontal_view_movement: Animation {
|
2024-03-05 13:32:30 +04:00
|
|
|
off: false,
|
|
|
|
|
kind: AnimationKind::Easing(EasingParams {
|
|
|
|
|
duration_ms: Some(100),
|
|
|
|
|
curve: Some(AnimationCurve::EaseOutExpo),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
window_open: Animation {
|
|
|
|
|
off: true,
|
2024-02-07 17:05:15 +04:00
|
|
|
..Animation::unfilled()
|
|
|
|
|
},
|
|
|
|
|
..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,
|
|
|
|
|
}],
|
|
|
|
|
excludes: vec![Match {
|
|
|
|
|
app_id: None,
|
|
|
|
|
title: Some(Regex::new("~").unwrap()),
|
|
|
|
|
}],
|
|
|
|
|
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-02-13 17:46:37 +04:00
|
|
|
..Default::default()
|
|
|
|
|
}],
|
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,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::Spawn(vec!["alacritty".to_owned()]),
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::CloseWindow,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::FocusMonitorLeft,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::MoveWindowToMonitorRight,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::ConsumeWindowIntoColumn,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
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,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::FocusWorkspace(1),
|
2023-09-16 12:14:02 +04:00
|
|
|
},
|
2024-02-12 07:53:06 +04:00
|
|
|
Bind {
|
|
|
|
|
key: Key {
|
|
|
|
|
keysym: Keysym::e,
|
|
|
|
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
|
|
|
|
},
|
2024-03-01 12:50:49 +01:00
|
|
|
action: Action::Quit(true),
|
2024-02-12 07:53:06 +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!(
|
|
|
|
|
"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
|
|
|
}
|