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

6169 lines
199 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::fs::{self, File};
use std::io::Write;
2025-02-10 13:29:00 +03:00
use std::ops::{Mul, MulAssign};
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 _;
use layer_rule::LayerRule;
use miette::{miette, Context, IntoDiagnostic};
2024-12-28 11:40:16 +03:00
use niri_ipc::{
2025-02-06 08:42:09 +03:00
ColumnDisplay, ConfiguredMode, LayoutSwitchTarget, PositionChange, SizeChange, Transform,
2024-12-28 11:40:16 +03:00
WorkspaceReferenceArg,
};
2024-08-20 11:43:32 +03:00
use smithay::backend::renderer::Color32F;
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
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.25, 0.25, 0.25, 1.]);
pub const DEFAULT_BACKDROP_COLOR: Color = Color::from_array_unpremul([0.15, 0.15, 0.15, 1.]);
2024-07-25 23:41:33 +05:30
pub mod layer_rule;
mod utils;
pub use utils::RegexEq;
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"))]
2024-07-04 17:51:11 +04:00
pub outputs: Outputs,
2023-09-21 19:58:03 +04:00
#[knuffel(children(name = "spawn-at-startup"))]
pub spawn_at_startup: Vec<SpawnAtStartup>,
2025-08-20 14:31:34 +03:00
#[knuffel(children(name = "spawn-at-startup-sh"))]
pub spawn_at_startup_sh: Vec<SpawnAtStartupSh>,
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)]
2025-01-22 00:00:35 -05:00
pub clipboard: Clipboard,
#[knuffel(child, default)]
pub hotkey_overlay: HotkeyOverlay,
#[knuffel(child, default)]
pub config_notification: ConfigNotification,
#[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)]
2025-02-16 08:46:38 +03:00
pub gestures: Gestures,
#[knuffel(child, default)]
2025-04-25 09:36:50 +03:00
pub overview: Overview,
#[knuffel(child, default)]
2024-02-24 10:08:56 +04:00
pub environment: Environment,
2025-06-04 08:26:51 +03:00
#[knuffel(child, default)]
pub xwayland_satellite: XwaylandSatellite,
2024-02-13 17:46:37 +04:00
#[knuffel(children(name = "window-rule"))]
pub window_rules: Vec<WindowRule>,
#[knuffel(children(name = "layer-rule"))]
pub layer_rules: Vec<LayerRule>,
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)]
2024-10-18 16:00:40 +02:00
pub switch_events: SwitchBinds,
#[knuffel(child, default)]
2023-09-06 15:49:46 +04:00
pub debug: DebugConfig,
2024-05-11 22:40:30 +02:00
#[knuffel(children(name = "workspace"))]
pub workspaces: Vec<Workspace>,
2023-09-05 12:58:51 +04:00
}
#[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)]
pub trackball: Trackball,
#[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: Option<WarpMouseToFocus>,
2024-03-18 18:17:04 +04:00
#[knuffel(child)]
pub focus_follows_mouse: Option<FocusFollowsMouse>,
2024-03-19 14:27:52 +00:00
#[knuffel(child)]
pub workspace_auto_back_and_forth: bool,
2025-02-05 09:34:25 -05:00
#[knuffel(child, unwrap(argument, str))]
pub mod_key: Option<ModKey>,
#[knuffel(child, unwrap(argument, str))]
pub mod_key_nested: Option<ModKey>,
2023-09-05 12:58:51 +04:00
}
2024-05-19 17:55:54 +04:00
#[derive(knuffel::Decode, Debug, PartialEq, Eq)]
2023-09-05 12:58:51 +04:00
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.
2024-05-19 17:55:54 +04:00
#[knuffel(child, unwrap(argument), default = Self::default().repeat_delay)]
pub repeat_delay: u16,
2024-05-19 17:55:54 +04:00
#[knuffel(child, unwrap(argument), default = Self::default().repeat_rate)]
pub repeat_rate: u8,
#[knuffel(child, unwrap(argument), default)]
pub track_layout: TrackLayout,
#[knuffel(child)]
pub numlock: bool,
2023-09-05 12:58:51 +04:00
}
2024-05-19 17:55:54 +04:00
impl Default for Keyboard {
fn default() -> Self {
Self {
xkb: Default::default(),
repeat_delay: 600,
repeat_rate: 25,
track_layout: Default::default(),
numlock: Default::default(),
2024-05-19 17:55:54 +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>,
#[knuffel(child, unwrap(argument))]
pub file: Option<String>,
2023-09-05 12:58:51 +04:00
}
2023-12-05 08:04:46 +04:00
impl Xkb {
2025-08-11 09:39:17 +03:00
pub fn to_xkb_config(&self) -> XkbConfig<'_> {
2023-12-05 08:04:46 +04:00
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,
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct ScrollFactor {
#[knuffel(argument)]
pub base: Option<FloatOrInt<0, 100>>,
#[knuffel(property)]
pub horizontal: Option<FloatOrInt<-100, 100>>,
#[knuffel(property)]
pub vertical: Option<FloatOrInt<-100, 100>>,
}
impl ScrollFactor {
pub fn h_v_factors(&self) -> (f64, f64) {
let base_value = self.base.map(|f| f.0).unwrap_or(1.0);
let h = self.horizontal.map(|f| f.0).unwrap_or(base_value);
let v = self.vertical.map(|f| f.0).unwrap_or(base_value);
(h, v)
}
}
2023-09-05 12:58:51 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Touchpad {
#[knuffel(child)]
pub off: bool,
2023-09-05 12:58:51 +04:00
#[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,
2025-03-01 23:01:34 -08:00
#[knuffel(child, unwrap(argument))]
pub drag: Option<bool>,
2024-02-03 08:20:15 +04:00
#[knuffel(child)]
2025-02-04 20:21:15 +08:00
pub drag_lock: 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: FloatOrInt<-1, 1>,
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 scroll_method: Option<ScrollMethod>,
#[knuffel(child, unwrap(argument))]
pub scroll_button: Option<u32>,
#[knuffel(child)]
pub scroll_button_lock: bool,
#[knuffel(child, unwrap(argument, str))]
2024-01-08 10:32:04 +04:00
pub tap_button_map: Option<TapButtonMap>,
2024-05-21 10:10:11 +04:00
#[knuffel(child)]
pub left_handed: bool,
#[knuffel(child)]
pub disabled_on_external_mouse: bool,
2024-07-13 07:34:22 +03:00
#[knuffel(child)]
pub middle_emulation: bool,
#[knuffel(child)]
pub scroll_factor: Option<ScrollFactor>,
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 off: bool,
2024-01-08 11:53:34 +04:00
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: FloatOrInt<-1, 1>,
2024-01-08 11:53:34 +04:00
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
#[knuffel(child, unwrap(argument, str))]
pub scroll_method: Option<ScrollMethod>,
#[knuffel(child, unwrap(argument))]
pub scroll_button: Option<u32>,
2024-05-21 10:10:11 +04:00
#[knuffel(child)]
pub scroll_button_lock: bool,
#[knuffel(child)]
2024-05-21 10:10:11 +04:00
pub left_handed: bool,
2024-07-13 07:34:22 +03:00
#[knuffel(child)]
pub middle_emulation: bool,
#[knuffel(child)]
pub scroll_factor: Option<ScrollFactor>,
2024-01-08 11:53:34 +04:00
}
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Trackpoint {
#[knuffel(child)]
pub off: bool,
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: FloatOrInt<-1, 1>,
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
#[knuffel(child, unwrap(argument, str))]
pub scroll_method: Option<ScrollMethod>,
#[knuffel(child, unwrap(argument))]
pub scroll_button: Option<u32>,
2024-07-13 07:34:22 +03:00
#[knuffel(child)]
pub scroll_button_lock: bool,
#[knuffel(child)]
pub left_handed: bool,
#[knuffel(child)]
2024-07-13 07:34:22 +03:00
pub middle_emulation: bool,
}
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Trackball {
#[knuffel(child)]
pub off: bool,
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: FloatOrInt<-1, 1>,
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
#[knuffel(child, unwrap(argument, str))]
pub scroll_method: Option<ScrollMethod>,
#[knuffel(child, unwrap(argument))]
pub scroll_button: Option<u32>,
#[knuffel(child)]
pub scroll_button_lock: bool,
#[knuffel(child)]
pub left_handed: bool,
#[knuffel(child)]
pub middle_emulation: bool,
}
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
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollMethod {
NoScroll,
TwoFinger,
Edge,
OnButtonDown,
}
impl From<ScrollMethod> for input::ScrollMethod {
fn from(value: ScrollMethod) -> Self {
match value {
ScrollMethod::NoScroll => Self::NoScroll,
ScrollMethod::TwoFinger => Self::TwoFinger,
ScrollMethod::Edge => Self::Edge,
ScrollMethod::OnButtonDown => Self::OnButtonDown,
}
}
}
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)]
pub off: bool,
#[knuffel(child, unwrap(arguments))]
pub calibration_matrix: Option<Vec<f32>>,
2023-10-03 17:02:07 +04:00
#[knuffel(child, unwrap(argument))]
pub map_to_output: Option<String>,
2024-05-21 10:10:11 +04:00
#[knuffel(child)]
pub left_handed: bool,
2023-10-03 17:02:07 +04:00
}
2024-02-24 18:32:13 +01:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Touch {
2025-02-21 13:35:10 -05:00
#[knuffel(child)]
pub off: bool,
2024-02-24 18:32:13 +01:00
#[knuffel(child, unwrap(argument))]
pub map_to_output: Option<String>,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct FocusFollowsMouse {
#[knuffel(property, str)]
pub max_scroll_amount: Option<Percent>,
}
#[derive(knuffel::Decode, Debug, PartialEq, Eq, Clone, Copy)]
pub struct WarpMouseToFocus {
#[knuffel(property, str)]
pub mode: Option<WarpMouseToFocusMode>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum WarpMouseToFocusMode {
CenterXy,
CenterXyAlways,
}
impl FromStr for WarpMouseToFocusMode {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"center-xy" => Ok(Self::CenterXy),
"center-xy-always" => Ok(Self::CenterXyAlways),
_ => Err(miette!(
r#"invalid mode for warp-mouse-to-focus, can be "center-xy" or "center-xy-always" (or leave unset for separate centering)"#
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Percent(pub f64);
2025-02-05 09:34:25 -05:00
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ModKey {
Ctrl,
Shift,
Alt,
Super,
IsoLevel3Shift,
IsoLevel5Shift,
}
impl ModKey {
pub fn to_modifiers(&self) -> Modifiers {
match self {
ModKey::Ctrl => Modifiers::CTRL,
ModKey::Shift => Modifiers::SHIFT,
ModKey::Alt => Modifiers::ALT,
ModKey::Super => Modifiers::SUPER,
ModKey::IsoLevel3Shift => Modifiers::ISO_LEVEL3_SHIFT,
ModKey::IsoLevel5Shift => Modifiers::ISO_LEVEL5_SHIFT,
}
}
}
2024-07-04 17:51:11 +04:00
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Outputs(pub Vec<Output>);
#[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,
2024-05-05 12:39:20 +04:00
#[knuffel(child, unwrap(argument))]
2024-06-18 11:01:18 +03:00
pub scale: Option<FloatOrInt<0, 10>>,
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)]
2024-08-22 18:58:07 +10:00
pub variable_refresh_rate: Option<Vrr>,
#[knuffel(child)]
pub focus_at_startup: bool,
2025-05-06 17:12:07 +03:00
#[knuffel(child)]
pub background_color: Option<Color>,
2025-04-28 07:53:03 +03:00
#[knuffel(child)]
pub backdrop_color: Option<Color>,
}
2024-08-22 18:58:07 +10:00
impl Output {
pub fn is_vrr_always_on(&self) -> bool {
self.variable_refresh_rate == Some(Vrr { on_demand: false })
}
pub fn is_vrr_on_demand(&self) -> bool {
self.variable_refresh_rate == Some(Vrr { on_demand: true })
}
pub fn is_vrr_always_off(&self) -> bool {
self.variable_refresh_rate.is_none()
}
}
impl Default for Output {
fn default() -> Self {
Self {
2023-12-18 10:27:41 +04:00
off: false,
focus_at_startup: false,
name: String::new(),
2024-05-05 12:39:20 +04:00
scale: None,
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-08-22 18:58:07 +10:00
variable_refresh_rate: None,
2025-05-06 17:12:07 +03:00
background_color: None,
2025-04-28 07:53:03 +03:00
backdrop_color: None,
}
}
}
#[derive(Debug, Clone)]
pub struct OutputName {
pub connector: String,
pub make: Option<String>,
pub model: Option<String>,
pub serial: Option<String>,
}
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-08-22 18:58:07 +10:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Default)]
pub struct Vrr {
#[knuffel(property, default = false)]
pub on_demand: bool,
}
2024-06-17 09:16:28 +03:00
// MIN and MAX generics are only used during parsing to check the value.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Layout {
#[knuffel(child, default)]
pub focus_ring: FocusRing,
#[knuffel(child, default)]
pub border: Border,
2024-07-15 15:51:48 +02:00
#[knuffel(child, default)]
2025-01-15 14:16:05 +03:00
pub shadow: Shadow,
#[knuffel(child, default)]
2025-02-02 08:41:42 +03:00
pub tab_indicator: TabIndicator,
#[knuffel(child, default)]
2024-07-15 15:51:48 +02:00
pub insert_hint: InsertHint,
#[knuffel(child, unwrap(children), default)]
2024-09-05 23:37:10 +02:00
pub preset_column_widths: Vec<PresetSize>,
#[knuffel(child)]
2024-09-05 23:37:10 +02:00
pub default_column_width: Option<DefaultPresetSize>,
#[knuffel(child, unwrap(children), default)]
pub preset_window_heights: Vec<PresetSize>,
2024-01-08 17:17:19 +04:00
#[knuffel(child, unwrap(argument), default)]
pub center_focused_column: CenterFocusedColumn,
#[knuffel(child)]
pub always_center_single_column: bool,
#[knuffel(child)]
pub empty_workspace_above_first: bool,
2025-02-06 08:42:09 +03:00
#[knuffel(child, unwrap(argument, str), default = Self::default().default_column_display)]
2025-02-01 10:46:52 +03:00
pub default_column_display: ColumnDisplay,
#[knuffel(child, unwrap(argument), default = Self::default().gaps)]
2024-06-17 09:16:28 +03:00
pub gaps: FloatOrInt<0, 65535>,
#[knuffel(child, default)]
pub struts: Struts,
2025-05-06 17:12:07 +03:00
#[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)]
pub background_color: Color,
}
impl Default for Layout {
fn default() -> Self {
Self {
focus_ring: Default::default(),
border: Default::default(),
2025-01-15 14:16:05 +03:00
shadow: Default::default(),
2025-02-02 08:41:42 +03:00
tab_indicator: Default::default(),
2024-07-15 15:51:48 +02:00
insert_hint: Default::default(),
preset_column_widths: Default::default(),
default_column_width: Default::default(),
center_focused_column: Default::default(),
always_center_single_column: false,
empty_workspace_above_first: false,
2025-02-06 08:42:09 +03:00
default_column_display: ColumnDisplay::Normal,
2024-06-17 09:16:28 +03:00
gaps: FloatOrInt(16.),
struts: Default::default(),
2024-09-05 23:37:10 +02:00
preset_window_heights: Default::default(),
2025-05-06 17:12:07 +03:00
background_color: DEFAULT_BACKGROUND_COLOR,
}
}
}
2023-09-21 19:58:03 +04:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct SpawnAtStartup {
#[knuffel(arguments)]
pub command: Vec<String>,
}
2025-08-20 14:31:34 +03:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct SpawnAtStartupSh {
#[knuffel(argument)]
pub command: 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)]
2024-06-17 09:16:28 +03:00
pub width: FloatOrInt<0, 65535>,
#[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,
2025-03-22 19:04:24 +01:00
#[knuffel(child, default = Self::default().urgent_color)]
pub urgent_color: Color,
2024-02-21 21:27:44 +04:00
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
2025-03-22 19:04:24 +01:00
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
2023-09-26 13:09:33 +04:00
}
impl Default for FocusRing {
fn default() -> Self {
Self {
off: false,
2024-06-17 09:16:28 +03:00
width: FloatOrInt(4.),
active_color: Color::from_rgba8_unpremul(127, 200, 255, 255),
inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
2025-03-22 19:04:24 +01:00
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
2024-02-21 21:27:44 +04:00
active_gradient: None,
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_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,
#[knuffel(property(name = "in"), str, default)]
pub in_: GradientInterpolation,
2024-02-21 21:27:44 +04:00
}
2025-02-02 08:41:42 +03:00
impl From<Color> for Gradient {
fn from(value: Color) -> Self {
Self {
from: value,
to: value,
angle: 0,
relative_to: GradientRelativeTo::Window,
in_: GradientInterpolation::default(),
}
}
}
2024-02-21 21:27:44 +04:00
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum GradientRelativeTo {
#[default]
Window,
WorkspaceView,
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct GradientInterpolation {
pub color_space: GradientColorSpace,
pub hue_interpolation: HueInterpolation,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum GradientColorSpace {
#[default]
Srgb,
SrgbLinear,
Oklab,
Oklch,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum HueInterpolation {
#[default]
Shorter,
Longer,
Increasing,
Decreasing,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Border {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = Self::default().width)]
2024-06-17 09:16:28 +03:00
pub width: FloatOrInt<0, 65535>,
#[knuffel(child, default = Self::default().active_color)]
pub active_color: Color,
#[knuffel(child, default = Self::default().inactive_color)]
pub inactive_color: Color,
2025-03-22 19:04:24 +01:00
#[knuffel(child, default = Self::default().urgent_color)]
pub urgent_color: Color,
2024-02-21 21:27:44 +04:00
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
2025-03-22 19:04:24 +01:00
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
}
impl Default for Border {
fn default() -> Self {
Self {
2024-02-12 09:22:22 +04:00
off: true,
2024-06-17 09:16:28 +03:00
width: FloatOrInt(4.),
active_color: Color::from_rgba8_unpremul(255, 200, 127, 255),
inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
2025-03-22 19:04:24 +01:00
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
2024-02-21 21:27:44 +04:00
active_gradient: None,
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_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,
2025-03-22 19:04:24 +01:00
urgent_color: value.urgent_color,
2024-02-21 21:27:44 +04:00
active_gradient: value.active_gradient,
inactive_gradient: value.inactive_gradient,
2025-03-22 19:04:24 +01:00
urgent_gradient: value.urgent_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,
2025-03-22 19:04:24 +01:00
urgent_color: value.urgent_color,
2024-04-24 22:17:53 +04:00
active_gradient: value.active_gradient,
inactive_gradient: value.inactive_gradient,
2025-03-22 19:04:24 +01:00
urgent_gradient: value.urgent_gradient,
2024-04-24 22:17:53 +04:00
}
}
}
2025-01-15 14:16:05 +03:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Shadow {
#[knuffel(child)]
pub on: bool,
#[knuffel(child, default = Self::default().offset)]
pub offset: ShadowOffset,
#[knuffel(child, unwrap(argument), default = Self::default().softness)]
pub softness: FloatOrInt<0, 1024>,
#[knuffel(child, unwrap(argument), default = Self::default().spread)]
2025-03-09 18:55:17 +01:00
pub spread: FloatOrInt<-1024, 1024>,
2025-01-15 14:16:05 +03:00
#[knuffel(child, unwrap(argument), default = Self::default().draw_behind_window)]
pub draw_behind_window: bool,
#[knuffel(child, default = Self::default().color)]
pub color: Color,
#[knuffel(child)]
pub inactive_color: Option<Color>,
}
impl Default for Shadow {
fn default() -> Self {
Self {
on: false,
offset: ShadowOffset {
x: FloatOrInt(0.),
y: FloatOrInt(5.),
},
softness: FloatOrInt(30.),
spread: FloatOrInt(5.),
draw_behind_window: false,
color: Color::from_rgba8_unpremul(0, 0, 0, 0x70),
inactive_color: None,
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct ShadowOffset {
#[knuffel(property, default)]
pub x: FloatOrInt<-65535, 65535>,
#[knuffel(property, default)]
pub y: FloatOrInt<-65535, 65535>,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct WorkspaceShadow {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, default = Self::default().offset)]
pub offset: ShadowOffset,
#[knuffel(child, unwrap(argument), default = Self::default().softness)]
pub softness: FloatOrInt<0, 1024>,
#[knuffel(child, unwrap(argument), default = Self::default().spread)]
pub spread: FloatOrInt<-1024, 1024>,
#[knuffel(child, default = Self::default().color)]
pub color: Color,
}
impl Default for WorkspaceShadow {
fn default() -> Self {
Self {
off: false,
offset: ShadowOffset {
x: FloatOrInt(0.),
y: FloatOrInt(10.),
},
softness: FloatOrInt(40.),
spread: FloatOrInt(10.),
color: Color::from_rgba8_unpremul(0, 0, 0, 0x50),
}
}
}
impl From<WorkspaceShadow> for Shadow {
fn from(value: WorkspaceShadow) -> Self {
Self {
on: !value.off,
offset: value.offset,
softness: value.softness,
spread: value.spread,
draw_behind_window: false,
color: value.color,
inactive_color: None,
}
}
}
2025-02-02 08:41:42 +03:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct TabIndicator {
#[knuffel(child)]
pub off: bool,
2025-02-05 08:09:48 +03:00
#[knuffel(child)]
pub hide_when_single_tab: bool,
#[knuffel(child)]
pub place_within_column: bool,
2025-02-02 08:41:42 +03:00
#[knuffel(child, unwrap(argument), default = Self::default().gap)]
pub gap: FloatOrInt<-65535, 65535>,
#[knuffel(child, unwrap(argument), default = Self::default().width)]
pub width: FloatOrInt<0, 65535>,
#[knuffel(child, default = Self::default().length)]
pub length: TabIndicatorLength,
2025-02-05 10:36:46 +03:00
#[knuffel(child, unwrap(argument), default = Self::default().position)]
pub position: TabIndicatorPosition,
2025-02-07 10:23:56 +03:00
#[knuffel(child, unwrap(argument), default = Self::default().gaps_between_tabs)]
pub gaps_between_tabs: FloatOrInt<0, 65535>,
2025-02-12 07:59:21 +03:00
#[knuffel(child, unwrap(argument), default = Self::default().corner_radius)]
pub corner_radius: FloatOrInt<0, 65535>,
2025-02-02 08:41:42 +03:00
#[knuffel(child)]
pub active_color: Option<Color>,
#[knuffel(child)]
pub inactive_color: Option<Color>,
#[knuffel(child)]
2025-05-10 22:34:53 +03:00
pub urgent_color: Option<Color>,
#[knuffel(child)]
2025-02-02 08:41:42 +03:00
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
2025-05-10 22:34:53 +03:00
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
2025-02-02 08:41:42 +03:00
}
impl Default for TabIndicator {
fn default() -> Self {
Self {
off: false,
2025-02-05 08:09:48 +03:00
hide_when_single_tab: false,
place_within_column: false,
2025-02-02 08:41:42 +03:00
gap: FloatOrInt(5.),
width: FloatOrInt(4.),
length: TabIndicatorLength {
total_proportion: Some(0.5),
},
2025-02-05 10:36:46 +03:00
position: TabIndicatorPosition::Left,
2025-02-07 10:23:56 +03:00
gaps_between_tabs: FloatOrInt(0.),
2025-02-12 07:59:21 +03:00
corner_radius: FloatOrInt(0.),
2025-02-02 08:41:42 +03:00
active_color: None,
inactive_color: None,
2025-05-10 22:34:53 +03:00
urgent_color: None,
2025-02-02 08:41:42 +03:00
active_gradient: None,
inactive_gradient: None,
2025-05-10 22:34:53 +03:00
urgent_gradient: None,
2025-02-02 08:41:42 +03:00
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct TabIndicatorLength {
#[knuffel(property)]
pub total_proportion: Option<f64>,
}
2025-02-05 10:36:46 +03:00
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
pub enum TabIndicatorPosition {
Left,
Right,
Top,
Bottom,
}
2024-07-15 15:51:48 +02:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct InsertHint {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, default = Self::default().color)]
pub color: Color,
2024-11-02 09:33:44 +03:00
#[knuffel(child)]
pub gradient: Option<Gradient>,
2024-07-15 15:51:48 +02:00
}
impl Default for InsertHint {
fn default() -> Self {
Self {
off: false,
color: Color::from_rgba8_unpremul(127, 200, 255, 128),
2024-11-02 09:33:44 +03:00
gradient: None,
2024-07-15 15:51:48 +02:00
}
}
}
/// RGB color in [0, 1] with unpremultiplied alpha.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
2023-09-26 13:09:33 +04:00
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
2023-09-26 13:09:33 +04:00
}
impl Color {
pub const fn new_unpremul(r: f32, g: f32, b: f32, a: f32) -> Self {
2023-09-26 13:09:33 +04:00
Self { r, g, b, a }
}
pub fn from_rgba8_unpremul(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::from_array_unpremul([r, g, b, a].map(|x| x as f32 / 255.))
}
pub fn from_array_premul([r, g, b, a]: [f32; 4]) -> Self {
let a = a.clamp(0., 1.);
if a == 0. {
Self::new_unpremul(0., 0., 0., 0.)
} else {
Self {
r: (r / a).clamp(0., 1.),
g: (g / a).clamp(0., 1.),
b: (b / a).clamp(0., 1.),
a,
}
}
}
2024-07-25 23:41:33 +05:30
pub const fn from_array_unpremul([r, g, b, a]: [f32; 4]) -> Self {
Self { r, g, b, a }
}
2024-08-20 11:43:32 +03:00
pub fn from_color32f(color: Color32F) -> Self {
Self::from_array_premul(color.components())
}
pub fn to_array_unpremul(self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
pub fn to_array_premul(self) -> [f32; 4] {
let [r, g, b, a] = [self.r, self.g, self.b, self.a];
[r * a, g * a, b * a, a]
2023-09-26 13:09:33 +04:00
}
}
2025-01-15 14:16:05 +03:00
impl Mul<f32> for Color {
type Output = Self;
fn mul(mut self, rhs: f32) -> Self::Output {
self.a *= rhs;
self
}
}
2025-02-10 13:29:00 +03:00
impl MulAssign<f32> for Color {
fn mul_assign(&mut self, rhs: f32) {
self.a *= rhs;
}
}
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,
#[knuffel(child)]
pub hide_when_typing: bool,
#[knuffel(child, unwrap(argument))]
pub hide_after_inactive_ms: Option<u32>,
2023-10-01 17:42:56 +04:00
}
impl Default for Cursor {
fn default() -> Self {
Self {
xcursor_theme: String::from("default"),
xcursor_size: 24,
hide_when_typing: false,
hide_after_inactive_ms: None,
2023-10-01 17:42:56 +04:00
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
2024-09-05 23:37:10 +02:00
pub enum PresetSize {
Proportion(#[knuffel(argument)] f64),
Fixed(#[knuffel(argument)] i32),
}
2025-01-31 19:24:26 +03:00
impl From<PresetSize> for SizeChange {
fn from(value: PresetSize) -> Self {
match value {
PresetSize::Proportion(prop) => SizeChange::SetProportion(prop * 100.),
PresetSize::Fixed(fixed) => SizeChange::SetFixed(fixed),
}
}
}
2024-12-27 09:58:22 +03:00
#[derive(Debug, Clone, Copy, PartialEq)]
2024-09-05 23:37:10 +02:00
pub struct DefaultPresetSize(pub Option<PresetSize>);
2023-11-02 21:07:29 +04:00
2024-06-17 09:16:28 +03:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
2023-12-21 08:37:30 +04:00
pub struct Struts {
#[knuffel(child, unwrap(argument), default)]
pub left: FloatOrInt<-65535, 65535>,
2023-12-21 08:37:30 +04:00
#[knuffel(child, unwrap(argument), default)]
pub right: FloatOrInt<-65535, 65535>,
2023-12-21 08:37:30 +04:00
#[knuffel(child, unwrap(argument), default)]
pub top: FloatOrInt<-65535, 65535>,
2023-12-21 08:37:30 +04:00
#[knuffel(child, unwrap(argument), default)]
pub bottom: FloatOrInt<-65535, 65535>,
2023-12-21 08:37:30 +04:00
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct HotkeyOverlay {
#[knuffel(child)]
pub skip_at_startup: bool,
#[knuffel(child)]
pub hide_not_bound: bool,
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct ConfigNotification {
#[knuffel(child)]
pub disable_failed: bool,
}
2025-01-22 00:00:35 -05:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Clipboard {
#[knuffel(child)]
pub disable_primary: 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 = FloatOrInt(1.))]
pub slowdown: FloatOrInt<0, { i32::MAX }>,
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-07-08 11:24:08 +04:00
#[knuffel(child, default)]
pub screenshot_ui_open: ScreenshotUiOpenAnim,
2025-04-25 09:36:50 +03:00
#[knuffel(child, default)]
pub overview_open_close: OverviewOpenCloseAnim,
2024-02-07 17:05:15 +04:00
}
impl Default for Animations {
fn default() -> Self {
Self {
off: false,
slowdown: FloatOrInt(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-07-08 11:24:08 +04:00
screenshot_ui_open: Default::default(),
2025-04-25 09:36:50 +03:00
overview_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-05-15 19:38:29 +04:00
#[derive(Debug, Clone, PartialEq)]
pub struct WindowOpenAnim {
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 WindowOpenAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
2024-05-15 19:38:29 +04:00
Self {
anim: Animation {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: 150,
curve: AnimationCurve::EaseOutExpo,
}),
},
custom_shader: None,
}
2024-04-08 18:23:18 +04:00
}
2024-04-17 14:06:32 +04:00
}
2024-04-08 18:23:18 +04:00
#[derive(Debug, Clone, PartialEq)]
pub struct WindowCloseAnim {
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 WindowCloseAnim {
2024-04-17 14:06:32 +04:00
fn default() -> Self {
Self {
anim: Animation {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: 150,
curve: AnimationCurve::EaseOutQuad,
}),
},
custom_shader: None,
}
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-07-08 11:24:08 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScreenshotUiOpenAnim(pub Animation);
impl Default for ScreenshotUiOpenAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: 200,
curve: AnimationCurve::EaseOutQuad,
}),
})
}
}
2025-04-25 09:36:50 +03:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OverviewOpenCloseAnim(pub Animation);
impl Default for OverviewOpenCloseAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
})
}
}
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-05-12 09:50:16 +04:00
Linear,
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,
}
2025-02-16 08:46:38 +03:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct Gestures {
#[knuffel(child, default)]
pub dnd_edge_view_scroll: DndEdgeViewScroll,
2025-04-25 10:02:31 +03:00
#[knuffel(child, default)]
pub dnd_edge_workspace_switch: DndEdgeWorkspaceSwitch,
#[knuffel(child, default)]
pub hot_corners: HotCorners,
2025-02-16 08:46:38 +03:00
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct DndEdgeViewScroll {
#[knuffel(child, unwrap(argument), default = Self::default().trigger_width)]
pub trigger_width: FloatOrInt<0, 65535>,
#[knuffel(child, unwrap(argument), default = Self::default().delay_ms)]
pub delay_ms: u16,
#[knuffel(child, unwrap(argument), default = Self::default().max_speed)]
pub max_speed: FloatOrInt<0, 1_000_000>,
}
impl Default for DndEdgeViewScroll {
fn default() -> Self {
Self {
trigger_width: FloatOrInt(30.), // Taken from GTK 4.
2025-02-19 07:49:09 +03:00
delay_ms: 100,
2025-02-16 08:46:38 +03:00
max_speed: FloatOrInt(1500.),
}
}
}
2025-04-25 10:02:31 +03:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct DndEdgeWorkspaceSwitch {
#[knuffel(child, unwrap(argument), default = Self::default().trigger_height)]
pub trigger_height: FloatOrInt<0, 65535>,
#[knuffel(child, unwrap(argument), default = Self::default().delay_ms)]
pub delay_ms: u16,
#[knuffel(child, unwrap(argument), default = Self::default().max_speed)]
pub max_speed: FloatOrInt<0, 1_000_000>,
}
impl Default for DndEdgeWorkspaceSwitch {
fn default() -> Self {
Self {
trigger_height: FloatOrInt(50.),
delay_ms: 100,
max_speed: FloatOrInt(1500.),
}
}
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct HotCorners {
#[knuffel(child)]
pub off: bool,
}
2025-04-25 09:36:50 +03:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Overview {
#[knuffel(child, unwrap(argument), default = Self::default().zoom)]
pub zoom: FloatOrInt<0, 1>,
2025-04-28 07:53:03 +03:00
#[knuffel(child, default = Self::default().backdrop_color)]
pub backdrop_color: Color,
#[knuffel(child, default)]
pub workspace_shadow: WorkspaceShadow,
2025-04-25 09:36:50 +03:00
}
impl Default for Overview {
fn default() -> Self {
Self {
zoom: FloatOrInt(0.5),
2025-04-28 07:53:03 +03:00
backdrop_color: DEFAULT_BACKDROP_COLOR,
workspace_shadow: WorkspaceShadow::default(),
2025-04-25 09:36:50 +03:00
}
}
}
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>,
}
2025-06-04 08:26:51 +03:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct XwaylandSatellite {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = Self::default().path)]
pub path: String,
}
impl Default for XwaylandSatellite {
fn default() -> Self {
Self {
off: false,
path: String::from("xwayland-satellite"),
}
}
}
2024-05-11 22:40:30 +02:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct Workspace {
#[knuffel(argument)]
pub name: WorkspaceName,
#[knuffel(child, unwrap(argument))]
pub open_on_output: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceName(pub 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)]
2024-09-05 23:37:10 +02:00
pub default_column_width: Option<DefaultPresetSize>,
2024-12-27 09:58:22 +03:00
#[knuffel(child)]
pub default_window_height: Option<DefaultPresetSize>,
2024-02-13 17:46:37 +04:00
#[knuffel(child, unwrap(argument))]
pub open_on_output: Option<String>,
2024-02-23 14:24:39 +04:00
#[knuffel(child, unwrap(argument))]
2024-05-11 22:40:30 +02:00
pub open_on_workspace: Option<String>,
#[knuffel(child, unwrap(argument))]
2024-02-23 14:24:39 +04:00
pub open_maximized: Option<bool>,
2024-02-24 08:44:21 +04:00
#[knuffel(child, unwrap(argument))]
pub open_fullscreen: Option<bool>,
2024-11-29 21:11:02 +03:00
#[knuffel(child, unwrap(argument))]
pub open_floating: Option<bool>,
2024-12-27 09:31:32 +03:00
#[knuffel(child, unwrap(argument))]
pub open_focused: 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,
2025-01-15 14:16:05 +03:00
#[knuffel(child, default)]
pub shadow: ShadowRule,
2025-02-02 08:41:42 +03:00
#[knuffel(child, default)]
pub tab_indicator: TabIndicatorRule,
#[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 baba_is_float: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub block_out_from: Option<BlockOutFrom>,
2024-08-22 18:58:07 +10:00
#[knuffel(child, unwrap(argument))]
pub variable_refresh_rate: Option<bool>,
2025-02-06 09:09:07 +03:00
#[knuffel(child, unwrap(argument, str))]
pub default_column_display: Option<ColumnDisplay>,
2024-12-29 10:39:21 +03:00
#[knuffel(child)]
2025-01-15 14:16:05 +03:00
pub default_floating_position: Option<FloatingPosition>,
2025-01-20 22:52:43 +02:00
#[knuffel(child, unwrap(argument))]
pub scroll_factor: Option<FloatOrInt<0, 100>>,
#[knuffel(child, unwrap(argument))]
pub tiled_state: Option<bool>,
2024-02-13 17:46:37 +04:00
}
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
2024-02-13 17:46:37 +04:00
pub struct Match {
#[knuffel(property, str)]
pub app_id: Option<RegexEq>,
2024-02-13 17:46:37 +04:00
#[knuffel(property, str)]
pub title: Option<RegexEq>,
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-04-22 22:51:52 +02:00
#[knuffel(property)]
pub is_active_in_column: Option<bool>,
2024-05-16 11:43:13 +04:00
#[knuffel(property)]
2024-12-27 11:20:03 +03:00
pub is_floating: Option<bool>,
#[knuffel(property)]
pub is_window_cast_target: Option<bool>,
#[knuffel(property)]
2025-05-10 22:48:11 +03:00
pub is_urgent: Option<bool>,
#[knuffel(property)]
2024-05-16 11:43:13 +04:00
pub at_startup: Option<bool>,
2024-02-13 17:46:37 +04:00
}
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,
]
}
}
2025-02-12 07:59:21 +03:00
impl From<f32> for CornerRadius {
fn from(value: f32) -> Self {
Self {
top_left: value,
top_right: value,
bottom_right: value,
bottom_left: value,
}
}
}
#[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))]
2024-06-17 09:16:28 +03:00
pub width: Option<FloatOrInt<0, 65535>>,
2024-04-24 21:49:07 +04:00
#[knuffel(child)]
pub active_color: Option<Color>,
#[knuffel(child)]
pub inactive_color: Option<Color>,
#[knuffel(child)]
2025-03-22 19:04:24 +01:00
pub urgent_color: Option<Color>,
#[knuffel(child)]
2024-04-24 21:49:07 +04:00
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
2025-03-22 19:04:24 +01:00
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
2024-04-24 21:49:07 +04:00
}
2025-01-15 14:16:05 +03:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct ShadowRule {
#[knuffel(child)]
pub off: bool,
#[knuffel(child)]
pub on: bool,
#[knuffel(child)]
pub offset: Option<ShadowOffset>,
#[knuffel(child, unwrap(argument))]
pub softness: Option<FloatOrInt<0, 1024>>,
#[knuffel(child, unwrap(argument))]
2025-03-09 18:55:17 +01:00
pub spread: Option<FloatOrInt<-1024, 1024>>,
2025-01-15 14:16:05 +03:00
#[knuffel(child, unwrap(argument))]
pub draw_behind_window: Option<bool>,
#[knuffel(child)]
pub color: Option<Color>,
#[knuffel(child)]
pub inactive_color: Option<Color>,
}
2025-02-02 08:41:42 +03:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct TabIndicatorRule {
#[knuffel(child)]
pub active_color: Option<Color>,
#[knuffel(child)]
pub inactive_color: Option<Color>,
#[knuffel(child)]
2025-05-10 22:34:53 +03:00
pub urgent_color: Option<Color>,
#[knuffel(child)]
2025-02-02 08:41:42 +03:00
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
2025-05-10 22:34:53 +03:00
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
2025-02-02 08:41:42 +03:00
}
2024-12-29 10:39:21 +03:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
2025-01-15 14:16:05 +03:00
pub struct FloatingPosition {
2024-12-29 10:39:21 +03:00
#[knuffel(property)]
pub x: FloatOrInt<-65535, 65535>,
#[knuffel(property)]
pub y: FloatOrInt<-65535, 65535>,
#[knuffel(property, default)]
pub relative_to: RelativeTo,
}
#[derive(knuffel::DecodeScalar, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum RelativeTo {
#[default]
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Top,
Bottom,
Left,
Right,
2024-12-29 10:39:21 +03:00
}
#[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-06-30 22:37:44 +05:00
pub repeat: bool,
2024-03-22 20:47:40 +04:00
pub cooldown: Option<Duration>,
pub allow_when_locked: bool,
pub allow_inhibiting: bool,
2025-02-13 08:45:23 +03:00
pub hotkey_overlay_title: Option<Option<String>>,
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),
MouseLeft,
MouseRight,
MouseMiddle,
MouseBack,
MouseForward,
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;
2024-07-31 11:00:35 -04:00
const SHIFT = 1 << 1;
const ALT = 1 << 2;
const SUPER = 1 << 3;
const ISO_LEVEL3_SHIFT = 1 << 4;
const ISO_LEVEL5_SHIFT = 1 << 5;
const COMPOSITOR = 1 << 6;
2023-09-05 12:58:51 +04:00
}
}
2024-10-18 16:00:40 +02:00
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
pub struct SwitchBinds {
#[knuffel(child)]
pub lid_open: Option<SwitchAction>,
#[knuffel(child)]
pub lid_close: Option<SwitchAction>,
#[knuffel(child)]
pub tablet_mode_on: Option<SwitchAction>,
#[knuffel(child)]
pub tablet_mode_off: Option<SwitchAction>,
}
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct SwitchAction {
#[knuffel(child, unwrap(arguments))]
pub spawn: Vec<String>,
}
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,
2024-10-09 01:50:06 -07:00
PowerOnMonitors,
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>),
2025-08-20 14:31:34 +03:00
SpawnSh(#[knuffel(argument)] String),
2024-05-07 22:06:43 +04:00
DoScreenTransition(#[knuffel(property(name = "delay-ms"))] Option<u16>),
2023-10-30 20:29:03 +04:00
#[knuffel(skip)]
ConfirmScreenshot {
write_to_disk: bool,
},
2023-10-30 20:29:03 +04:00
#[knuffel(skip)]
CancelScreenshot,
#[knuffel(skip)]
ScreenshotTogglePointer,
Screenshot(#[knuffel(property(name = "show-pointer"), default = true)] bool),
ScreenshotScreen(
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
#[knuffel(property(name = "show-pointer"), default = true)] bool,
),
ScreenshotWindow(#[knuffel(property(name = "write-to-disk"), default = true)] bool),
#[knuffel(skip)]
ScreenshotWindowById {
id: u64,
write_to_disk: bool,
},
ToggleKeyboardShortcutsInhibit,
2023-09-05 12:58:51 +04:00
CloseWindow,
#[knuffel(skip)]
CloseWindowById(u64),
2023-09-05 12:58:51 +04:00
FullscreenWindow,
#[knuffel(skip)]
FullscreenWindowById(u64),
2025-03-17 14:56:29 +03:00
ToggleWindowedFullscreen,
#[knuffel(skip)]
ToggleWindowedFullscreenById(u64),
#[knuffel(skip)]
FocusWindow(u64),
FocusWindowInColumn(#[knuffel(argument)] u8),
2024-11-21 12:48:51 +01:00
FocusWindowPrevious,
2023-09-05 12:58:51 +04:00
FocusColumnLeft,
2025-04-25 09:47:45 +03:00
#[knuffel(skip)]
FocusColumnLeftUnderMouse,
2023-09-05 12:58:51 +04:00
FocusColumnRight,
2025-04-25 09:47:45 +03:00
#[knuffel(skip)]
FocusColumnRightUnderMouse,
2023-12-29 07:51:14 +04:00
FocusColumnFirst,
FocusColumnLast,
FocusColumnRightOrFirst,
FocusColumnLeftOrLast,
2025-03-13 03:05:55 +01:00
FocusColumn(#[knuffel(argument)] usize),
FocusWindowOrMonitorUp,
FocusWindowOrMonitorDown,
FocusColumnOrMonitorLeft,
FocusColumnOrMonitorRight,
2023-09-05 12:58:51 +04:00
FocusWindowDown,
FocusWindowUp,
FocusWindowDownOrColumnLeft,
FocusWindowDownOrColumnRight,
FocusWindowUpOrColumnLeft,
FocusWindowUpOrColumnRight,
FocusWindowOrWorkspaceDown,
FocusWindowOrWorkspaceUp,
FocusWindowTop,
FocusWindowBottom,
FocusWindowDownOrTop,
FocusWindowUpOrBottom,
2023-09-05 12:58:51 +04:00
MoveColumnLeft,
MoveColumnRight,
2023-12-29 08:01:02 +04:00
MoveColumnToFirst,
MoveColumnToLast,
MoveColumnLeftOrToMonitorLeft,
MoveColumnRightOrToMonitorRight,
2025-03-13 03:09:06 +01:00
MoveColumnToIndex(#[knuffel(argument)] usize),
2023-09-05 12:58:51 +04:00
MoveWindowDown,
MoveWindowUp,
MoveWindowDownOrToWorkspaceDown,
MoveWindowUpOrToWorkspaceUp,
ConsumeOrExpelWindowLeft,
#[knuffel(skip)]
ConsumeOrExpelWindowLeftById(u64),
ConsumeOrExpelWindowRight,
#[knuffel(skip)]
ConsumeOrExpelWindowRightById(u64),
2023-09-05 12:58:51 +04:00
ConsumeWindowIntoColumn,
ExpelWindowFromColumn,
2025-01-09 08:29:36 +00:00
SwapWindowLeft,
SwapWindowRight,
2025-02-01 10:46:52 +03:00
ToggleColumnTabbedDisplay,
2025-02-06 08:42:09 +03:00
SetColumnDisplay(#[knuffel(argument, str)] ColumnDisplay),
CenterColumn,
2024-12-29 22:44:19 +03:00
CenterWindow,
#[knuffel(skip)]
CenterWindowById(u64),
2025-05-12 14:11:38 +03:00
CenterVisibleColumns,
2023-09-05 12:58:51 +04:00
FocusWorkspaceDown,
2025-04-25 09:47:45 +03:00
#[knuffel(skip)]
FocusWorkspaceDownUnderMouse,
2023-09-05 12:58:51 +04:00
FocusWorkspaceUp,
2025-04-25 09:47:45 +03:00
#[knuffel(skip)]
FocusWorkspaceUpUnderMouse,
2024-05-11 22:40:30 +02:00
FocusWorkspace(#[knuffel(argument)] WorkspaceReference),
2024-03-19 14:27:52 +00:00
FocusWorkspacePrevious,
2023-09-05 12:58:51 +04:00
MoveWindowToWorkspaceDown,
MoveWindowToWorkspaceUp,
MoveWindowToWorkspace(
#[knuffel(argument)] WorkspaceReference,
#[knuffel(property(name = "focus"), default = true)] bool,
),
#[knuffel(skip)]
MoveWindowToWorkspaceById {
window_id: u64,
reference: WorkspaceReference,
focus: bool,
},
MoveColumnToWorkspaceDown(#[knuffel(property(name = "focus"), default = true)] bool),
MoveColumnToWorkspaceUp(#[knuffel(property(name = "focus"), default = true)] bool),
MoveColumnToWorkspace(
#[knuffel(argument)] WorkspaceReference,
#[knuffel(property(name = "focus"), default = true)] bool,
),
2023-10-14 20:42:10 +04:00
MoveWorkspaceDown,
MoveWorkspaceUp,
MoveWorkspaceToIndex(#[knuffel(argument)] usize),
#[knuffel(skip)]
MoveWorkspaceToIndexByRef {
new_idx: usize,
reference: WorkspaceReference,
},
#[knuffel(skip)]
MoveWorkspaceToMonitorByRef {
output_name: String,
reference: WorkspaceReference,
},
MoveWorkspaceToMonitor(#[knuffel(argument)] String),
SetWorkspaceName(#[knuffel(argument)] String),
#[knuffel(skip)]
SetWorkspaceNameByRef {
name: String,
reference: WorkspaceReference,
},
UnsetWorkspaceName,
#[knuffel(skip)]
UnsetWorkSpaceNameByRef(#[knuffel(argument)] WorkspaceReference),
2023-09-05 12:58:51 +04:00
FocusMonitorLeft,
FocusMonitorRight,
FocusMonitorDown,
FocusMonitorUp,
FocusMonitorPrevious,
FocusMonitorNext,
FocusMonitor(#[knuffel(argument)] String),
2023-09-05 12:58:51 +04:00
MoveWindowToMonitorLeft,
MoveWindowToMonitorRight,
MoveWindowToMonitorDown,
MoveWindowToMonitorUp,
MoveWindowToMonitorPrevious,
MoveWindowToMonitorNext,
MoveWindowToMonitor(#[knuffel(argument)] String),
2025-03-02 12:09:44 +01:00
#[knuffel(skip)]
MoveWindowToMonitorById {
id: u64,
output: String,
},
2024-01-15 10:36:59 +04:00
MoveColumnToMonitorLeft,
MoveColumnToMonitorRight,
MoveColumnToMonitorDown,
MoveColumnToMonitorUp,
MoveColumnToMonitorPrevious,
MoveColumnToMonitorNext,
MoveColumnToMonitor(#[knuffel(argument)] String),
2024-12-28 10:12:50 +03:00
SetWindowWidth(#[knuffel(argument, str)] SizeChange),
#[knuffel(skip)]
SetWindowWidthById {
id: u64,
change: SizeChange,
},
2023-11-08 11:17:06 +04:00
SetWindowHeight(#[knuffel(argument, str)] SizeChange),
#[knuffel(skip)]
SetWindowHeightById {
id: u64,
change: SizeChange,
},
2024-05-11 09:33:23 +04:00
ResetWindowHeight,
#[knuffel(skip)]
ResetWindowHeightById(u64),
2023-09-05 12:58:51 +04:00
SwitchPresetColumnWidth,
2024-12-30 09:05:35 +03:00
SwitchPresetWindowWidth,
#[knuffel(skip)]
SwitchPresetWindowWidthById(u64),
2024-09-05 23:37:10 +02:00
SwitchPresetWindowHeight,
2024-09-12 11:53:10 +03:00
#[knuffel(skip)]
SwitchPresetWindowHeightById(u64),
2023-09-05 12:58:51 +04:00
MaximizeColumn,
2023-10-03 11:38:42 +04:00
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
2025-02-17 21:22:10 +03:00
ExpandColumnToAvailableWidth,
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,
MoveWorkspaceToMonitorPrevious,
MoveWorkspaceToMonitorNext,
2024-11-29 21:11:02 +03:00
ToggleWindowFloating,
#[knuffel(skip)]
ToggleWindowFloatingById(u64),
2024-12-28 10:13:30 +03:00
MoveWindowToFloating,
#[knuffel(skip)]
MoveWindowToFloatingById(u64),
MoveWindowToTiling,
#[knuffel(skip)]
MoveWindowToTilingById(u64),
2024-12-28 10:14:02 +03:00
FocusFloating,
FocusTiling,
2024-11-29 21:11:02 +03:00
SwitchFocusBetweenFloatingAndTiling,
2024-12-28 11:40:16 +03:00
#[knuffel(skip)]
MoveFloatingWindowById {
id: Option<u64>,
x: PositionChange,
y: PositionChange,
},
2025-01-23 10:40:52 +03:00
ToggleWindowRuleOpacity,
#[knuffel(skip)]
ToggleWindowRuleOpacityById(u64),
2025-03-15 11:23:01 +03:00
SetDynamicCastWindow,
#[knuffel(skip)]
SetDynamicCastWindowById(u64),
SetDynamicCastMonitor(#[knuffel(argument)] Option<String>),
ClearDynamicCastTarget,
2025-04-25 09:36:50 +03:00
ToggleOverview,
OpenOverview,
CloseOverview,
2025-03-27 19:38:14 +01:00
#[knuffel(skip)]
ToggleWindowUrgent(u64),
2025-03-27 19:38:14 +01:00
#[knuffel(skip)]
SetWindowUrgent(u64),
2025-03-27 19:38:14 +01:00
#[knuffel(skip)]
UnsetWindowUrgent(u64),
2025-08-09 16:20:08 +04:00
#[knuffel(skip)]
LoadConfigFile,
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),
niri_ipc::Action::PowerOffMonitors {} => Self::PowerOffMonitors,
2024-10-09 01:50:06 -07:00
niri_ipc::Action::PowerOnMonitors {} => Self::PowerOnMonitors,
2024-02-10 09:33:32 +04:00
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
2025-08-20 14:31:34 +03:00
niri_ipc::Action::SpawnSh { command } => Self::SpawnSh(command),
2024-05-07 22:06:43 +04:00
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
niri_ipc::Action::Screenshot { show_pointer } => Self::Screenshot(show_pointer),
niri_ipc::Action::ScreenshotScreen {
write_to_disk,
show_pointer,
} => Self::ScreenshotScreen(write_to_disk, show_pointer),
niri_ipc::Action::ScreenshotWindow {
id: None,
write_to_disk,
} => Self::ScreenshotWindow(write_to_disk),
niri_ipc::Action::ScreenshotWindow {
id: Some(id),
write_to_disk,
} => Self::ScreenshotWindowById { id, write_to_disk },
niri_ipc::Action::ToggleKeyboardShortcutsInhibit {} => {
Self::ToggleKeyboardShortcutsInhibit
}
niri_ipc::Action::CloseWindow { id: None } => Self::CloseWindow,
niri_ipc::Action::CloseWindow { id: Some(id) } => Self::CloseWindowById(id),
niri_ipc::Action::FullscreenWindow { id: None } => Self::FullscreenWindow,
niri_ipc::Action::FullscreenWindow { id: Some(id) } => Self::FullscreenWindowById(id),
2025-03-17 14:56:29 +03:00
niri_ipc::Action::ToggleWindowedFullscreen { id: None } => {
Self::ToggleWindowedFullscreen
}
niri_ipc::Action::ToggleWindowedFullscreen { id: Some(id) } => {
Self::ToggleWindowedFullscreenById(id)
}
niri_ipc::Action::FocusWindow { id } => Self::FocusWindow(id),
niri_ipc::Action::FocusWindowInColumn { index } => Self::FocusWindowInColumn(index),
2024-11-21 12:48:51 +01:00
niri_ipc::Action::FocusWindowPrevious {} => Self::FocusWindowPrevious,
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::FocusColumnRightOrFirst {} => Self::FocusColumnRightOrFirst,
niri_ipc::Action::FocusColumnLeftOrLast {} => Self::FocusColumnLeftOrLast,
2025-03-13 03:05:55 +01:00
niri_ipc::Action::FocusColumn { index } => Self::FocusColumn(index),
niri_ipc::Action::FocusWindowOrMonitorUp {} => Self::FocusWindowOrMonitorUp,
niri_ipc::Action::FocusWindowOrMonitorDown {} => Self::FocusWindowOrMonitorDown,
niri_ipc::Action::FocusColumnOrMonitorLeft {} => Self::FocusColumnOrMonitorLeft,
niri_ipc::Action::FocusColumnOrMonitorRight {} => Self::FocusColumnOrMonitorRight,
niri_ipc::Action::FocusWindowDown {} => Self::FocusWindowDown,
niri_ipc::Action::FocusWindowUp {} => Self::FocusWindowUp,
niri_ipc::Action::FocusWindowDownOrColumnLeft {} => Self::FocusWindowDownOrColumnLeft,
niri_ipc::Action::FocusWindowDownOrColumnRight {} => Self::FocusWindowDownOrColumnRight,
niri_ipc::Action::FocusWindowUpOrColumnLeft {} => Self::FocusWindowUpOrColumnLeft,
niri_ipc::Action::FocusWindowUpOrColumnRight {} => Self::FocusWindowUpOrColumnRight,
niri_ipc::Action::FocusWindowOrWorkspaceDown {} => Self::FocusWindowOrWorkspaceDown,
niri_ipc::Action::FocusWindowOrWorkspaceUp {} => Self::FocusWindowOrWorkspaceUp,
niri_ipc::Action::FocusWindowTop {} => Self::FocusWindowTop,
niri_ipc::Action::FocusWindowBottom {} => Self::FocusWindowBottom,
niri_ipc::Action::FocusWindowDownOrTop {} => Self::FocusWindowDownOrTop,
niri_ipc::Action::FocusWindowUpOrBottom {} => Self::FocusWindowUpOrBottom,
niri_ipc::Action::MoveColumnLeft {} => Self::MoveColumnLeft,
niri_ipc::Action::MoveColumnRight {} => Self::MoveColumnRight,
niri_ipc::Action::MoveColumnToFirst {} => Self::MoveColumnToFirst,
niri_ipc::Action::MoveColumnToLast {} => Self::MoveColumnToLast,
2025-03-13 03:09:06 +01:00
niri_ipc::Action::MoveColumnToIndex { index } => Self::MoveColumnToIndex(index),
niri_ipc::Action::MoveColumnLeftOrToMonitorLeft {} => {
Self::MoveColumnLeftOrToMonitorLeft
}
niri_ipc::Action::MoveColumnRightOrToMonitorRight {} => {
Self::MoveColumnRightOrToMonitorRight
}
niri_ipc::Action::MoveWindowDown {} => Self::MoveWindowDown,
niri_ipc::Action::MoveWindowUp {} => Self::MoveWindowUp,
niri_ipc::Action::MoveWindowDownOrToWorkspaceDown {} => {
2024-02-10 09:33:32 +04:00
Self::MoveWindowDownOrToWorkspaceDown
}
niri_ipc::Action::MoveWindowUpOrToWorkspaceUp {} => Self::MoveWindowUpOrToWorkspaceUp,
niri_ipc::Action::ConsumeOrExpelWindowLeft { id: None } => {
Self::ConsumeOrExpelWindowLeft
}
niri_ipc::Action::ConsumeOrExpelWindowLeft { id: Some(id) } => {
Self::ConsumeOrExpelWindowLeftById(id)
}
niri_ipc::Action::ConsumeOrExpelWindowRight { id: None } => {
Self::ConsumeOrExpelWindowRight
}
niri_ipc::Action::ConsumeOrExpelWindowRight { id: Some(id) } => {
Self::ConsumeOrExpelWindowRightById(id)
}
niri_ipc::Action::ConsumeWindowIntoColumn {} => Self::ConsumeWindowIntoColumn,
niri_ipc::Action::ExpelWindowFromColumn {} => Self::ExpelWindowFromColumn,
2025-01-09 08:29:36 +00:00
niri_ipc::Action::SwapWindowRight {} => Self::SwapWindowRight,
niri_ipc::Action::SwapWindowLeft {} => Self::SwapWindowLeft,
2025-02-01 10:46:52 +03:00
niri_ipc::Action::ToggleColumnTabbedDisplay {} => Self::ToggleColumnTabbedDisplay,
2025-02-06 08:42:09 +03:00
niri_ipc::Action::SetColumnDisplay { display } => Self::SetColumnDisplay(display),
niri_ipc::Action::CenterColumn {} => Self::CenterColumn,
2024-12-29 22:44:19 +03:00
niri_ipc::Action::CenterWindow { id: None } => Self::CenterWindow,
niri_ipc::Action::CenterWindow { id: Some(id) } => Self::CenterWindowById(id),
2025-05-12 14:11:38 +03:00
niri_ipc::Action::CenterVisibleColumns {} => Self::CenterVisibleColumns,
niri_ipc::Action::FocusWorkspaceDown {} => Self::FocusWorkspaceDown,
niri_ipc::Action::FocusWorkspaceUp {} => Self::FocusWorkspaceUp,
2024-05-11 22:40:30 +02:00
niri_ipc::Action::FocusWorkspace { reference } => {
Self::FocusWorkspace(WorkspaceReference::from(reference))
}
niri_ipc::Action::FocusWorkspacePrevious {} => Self::FocusWorkspacePrevious,
niri_ipc::Action::MoveWindowToWorkspaceDown {} => Self::MoveWindowToWorkspaceDown,
niri_ipc::Action::MoveWindowToWorkspaceUp {} => Self::MoveWindowToWorkspaceUp,
niri_ipc::Action::MoveWindowToWorkspace {
window_id: None,
reference,
focus,
} => Self::MoveWindowToWorkspace(WorkspaceReference::from(reference), focus),
niri_ipc::Action::MoveWindowToWorkspace {
window_id: Some(window_id),
reference,
focus,
} => Self::MoveWindowToWorkspaceById {
window_id,
reference: WorkspaceReference::from(reference),
focus,
},
niri_ipc::Action::MoveColumnToWorkspaceDown { focus } => {
Self::MoveColumnToWorkspaceDown(focus)
}
niri_ipc::Action::MoveColumnToWorkspaceUp { focus } => {
Self::MoveColumnToWorkspaceUp(focus)
}
niri_ipc::Action::MoveColumnToWorkspace { reference, focus } => {
Self::MoveColumnToWorkspace(WorkspaceReference::from(reference), focus)
2024-05-11 22:40:30 +02:00
}
niri_ipc::Action::MoveWorkspaceDown {} => Self::MoveWorkspaceDown,
niri_ipc::Action::MoveWorkspaceUp {} => Self::MoveWorkspaceUp,
niri_ipc::Action::SetWorkspaceName {
name,
workspace: None,
} => Self::SetWorkspaceName(name),
niri_ipc::Action::SetWorkspaceName {
name,
workspace: Some(reference),
} => Self::SetWorkspaceNameByRef {
name,
reference: WorkspaceReference::from(reference),
},
niri_ipc::Action::UnsetWorkspaceName { reference: None } => Self::UnsetWorkspaceName,
niri_ipc::Action::UnsetWorkspaceName {
reference: Some(reference),
} => Self::UnsetWorkSpaceNameByRef(WorkspaceReference::from(reference)),
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::FocusMonitorPrevious {} => Self::FocusMonitorPrevious,
niri_ipc::Action::FocusMonitorNext {} => Self::FocusMonitorNext,
niri_ipc::Action::FocusMonitor { output } => Self::FocusMonitor(output),
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::MoveWindowToMonitorPrevious {} => Self::MoveWindowToMonitorPrevious,
niri_ipc::Action::MoveWindowToMonitorNext {} => Self::MoveWindowToMonitorNext,
2025-03-02 12:09:44 +01:00
niri_ipc::Action::MoveWindowToMonitor { id: None, output } => {
Self::MoveWindowToMonitor(output)
}
niri_ipc::Action::MoveWindowToMonitor {
id: Some(id),
output,
} => Self::MoveWindowToMonitorById { id, output },
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::MoveColumnToMonitorPrevious {} => Self::MoveColumnToMonitorPrevious,
niri_ipc::Action::MoveColumnToMonitorNext {} => Self::MoveColumnToMonitorNext,
niri_ipc::Action::MoveColumnToMonitor { output } => Self::MoveColumnToMonitor(output),
2024-12-28 10:12:50 +03:00
niri_ipc::Action::SetWindowWidth { id: None, change } => Self::SetWindowWidth(change),
niri_ipc::Action::SetWindowWidth {
id: Some(id),
change,
} => Self::SetWindowWidthById { id, change },
niri_ipc::Action::SetWindowHeight { id: None, change } => Self::SetWindowHeight(change),
niri_ipc::Action::SetWindowHeight {
id: Some(id),
change,
} => Self::SetWindowHeightById { id, change },
niri_ipc::Action::ResetWindowHeight { id: None } => Self::ResetWindowHeight,
niri_ipc::Action::ResetWindowHeight { id: Some(id) } => Self::ResetWindowHeightById(id),
niri_ipc::Action::SwitchPresetColumnWidth {} => Self::SwitchPresetColumnWidth,
2024-12-30 09:05:35 +03:00
niri_ipc::Action::SwitchPresetWindowWidth { id: None } => Self::SwitchPresetWindowWidth,
niri_ipc::Action::SwitchPresetWindowWidth { id: Some(id) } => {
Self::SwitchPresetWindowWidthById(id)
}
2024-09-12 11:53:10 +03:00
niri_ipc::Action::SwitchPresetWindowHeight { id: None } => {
Self::SwitchPresetWindowHeight
}
niri_ipc::Action::SwitchPresetWindowHeight { id: Some(id) } => {
Self::SwitchPresetWindowHeightById(id)
}
niri_ipc::Action::MaximizeColumn {} => Self::MaximizeColumn,
2024-02-10 09:33:32 +04:00
niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change),
2025-02-17 21:22:10 +03:00
niri_ipc::Action::ExpandColumnToAvailableWidth {} => Self::ExpandColumnToAvailableWidth,
2024-02-10 09:33:32 +04:00
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::MoveWorkspaceToMonitorPrevious {} => {
Self::MoveWorkspaceToMonitorPrevious
}
niri_ipc::Action::MoveWorkspaceToIndex {
index,
reference: Some(reference),
} => Self::MoveWorkspaceToIndexByRef {
new_idx: index,
reference: WorkspaceReference::from(reference),
},
niri_ipc::Action::MoveWorkspaceToIndex {
index,
reference: None,
} => Self::MoveWorkspaceToIndex(index),
niri_ipc::Action::MoveWorkspaceToMonitor {
output,
reference: Some(reference),
} => Self::MoveWorkspaceToMonitorByRef {
output_name: output,
reference: WorkspaceReference::from(reference),
},
niri_ipc::Action::MoveWorkspaceToMonitor {
output,
reference: None,
} => Self::MoveWorkspaceToMonitor(output),
niri_ipc::Action::MoveWorkspaceToMonitorNext {} => Self::MoveWorkspaceToMonitorNext,
niri_ipc::Action::ToggleDebugTint {} => Self::ToggleDebugTint,
niri_ipc::Action::DebugToggleOpaqueRegions {} => Self::DebugToggleOpaqueRegions,
niri_ipc::Action::DebugToggleDamage {} => Self::DebugToggleDamage,
2024-11-29 21:11:02 +03:00
niri_ipc::Action::ToggleWindowFloating { id: None } => Self::ToggleWindowFloating,
niri_ipc::Action::ToggleWindowFloating { id: Some(id) } => {
Self::ToggleWindowFloatingById(id)
}
2024-12-28 10:13:30 +03:00
niri_ipc::Action::MoveWindowToFloating { id: None } => Self::MoveWindowToFloating,
niri_ipc::Action::MoveWindowToFloating { id: Some(id) } => {
Self::MoveWindowToFloatingById(id)
}
niri_ipc::Action::MoveWindowToTiling { id: None } => Self::MoveWindowToTiling,
niri_ipc::Action::MoveWindowToTiling { id: Some(id) } => {
Self::MoveWindowToTilingById(id)
}
2024-12-28 10:14:02 +03:00
niri_ipc::Action::FocusFloating {} => Self::FocusFloating,
niri_ipc::Action::FocusTiling {} => Self::FocusTiling,
2024-11-29 21:11:02 +03:00
niri_ipc::Action::SwitchFocusBetweenFloatingAndTiling {} => {
Self::SwitchFocusBetweenFloatingAndTiling
}
2024-12-28 11:40:16 +03:00
niri_ipc::Action::MoveFloatingWindow { id, x, y } => {
Self::MoveFloatingWindowById { id, x, y }
}
2025-01-23 10:40:52 +03:00
niri_ipc::Action::ToggleWindowRuleOpacity { id: None } => Self::ToggleWindowRuleOpacity,
niri_ipc::Action::ToggleWindowRuleOpacity { id: Some(id) } => {
Self::ToggleWindowRuleOpacityById(id)
}
2025-03-15 11:23:01 +03:00
niri_ipc::Action::SetDynamicCastWindow { id: None } => Self::SetDynamicCastWindow,
niri_ipc::Action::SetDynamicCastWindow { id: Some(id) } => {
Self::SetDynamicCastWindowById(id)
}
niri_ipc::Action::SetDynamicCastMonitor { output } => {
Self::SetDynamicCastMonitor(output)
}
niri_ipc::Action::ClearDynamicCastTarget {} => Self::ClearDynamicCastTarget,
2025-04-25 09:36:50 +03:00
niri_ipc::Action::ToggleOverview {} => Self::ToggleOverview,
niri_ipc::Action::OpenOverview {} => Self::OpenOverview,
niri_ipc::Action::CloseOverview {} => Self::CloseOverview,
niri_ipc::Action::ToggleWindowUrgent { id } => Self::ToggleWindowUrgent(id),
niri_ipc::Action::SetWindowUrgent { id } => Self::SetWindowUrgent(id),
niri_ipc::Action::UnsetWindowUrgent { id } => Self::UnsetWindowUrgent(id),
2025-08-09 16:20:08 +04:00
niri_ipc::Action::LoadConfigFile {} => Self::LoadConfigFile,
2024-02-10 09:33:32 +04:00
}
}
2023-11-02 00:09:31 +04:00
}
2024-05-11 22:40:30 +02:00
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum WorkspaceReference {
2024-09-02 09:20:23 +03:00
Id(u64),
2024-05-11 22:40:30 +02:00
Index(u8),
Name(String),
}
impl From<WorkspaceReferenceArg> for WorkspaceReference {
fn from(reference: WorkspaceReferenceArg) -> WorkspaceReference {
match reference {
2024-09-02 09:20:23 +03:00
WorkspaceReferenceArg::Id(id) => Self::Id(id),
2024-05-11 22:40:30 +02:00
WorkspaceReferenceArg::Index(i) => Self::Index(i),
WorkspaceReferenceArg::Name(n) => Self::Name(n),
}
}
}
impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceReference {
fn type_check(
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
ctx: &mut knuffel::decode::Context<S>,
) {
if let Some(type_name) = &type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
}
fn raw_decode(
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<WorkspaceReference, DecodeError<S>> {
match &**val {
knuffel::ast::Literal::String(ref s) => Ok(WorkspaceReference::Name(s.clone().into())),
knuffel::ast::Literal::Int(ref value) => match value.try_into() {
Ok(v) => Ok(WorkspaceReference::Index(v)),
Err(e) => {
ctx.emit_error(DecodeError::conversion(val, e));
Ok(WorkspaceReference::Index(0))
}
},
_ => {
ctx.emit_error(DecodeError::unsupported(
val,
"Unsupported value, only numbers and strings are recognized",
));
Ok(WorkspaceReference::Index(0))
}
}
}
}
2024-06-17 09:16:28 +03:00
impl<S: knuffel::traits::ErrorSpan, const MIN: i32, const MAX: i32> knuffel::DecodeScalar<S>
for FloatOrInt<MIN, MAX>
{
fn type_check(
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
ctx: &mut knuffel::decode::Context<S>,
) {
if let Some(type_name) = &type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
}
fn raw_decode(
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
match &**val {
knuffel::ast::Literal::Int(ref value) => match value.try_into() {
Ok(v) => {
if (MIN..=MAX).contains(&v) {
Ok(FloatOrInt(f64::from(v)))
} else {
ctx.emit_error(DecodeError::conversion(
val,
format!("value must be between {MIN} and {MAX}"),
));
Ok(FloatOrInt::default())
}
}
Err(e) => {
ctx.emit_error(DecodeError::conversion(val, e));
Ok(FloatOrInt::default())
}
},
knuffel::ast::Literal::Decimal(ref value) => match value.try_into() {
Ok(v) => {
if (f64::from(MIN)..=f64::from(MAX)).contains(&v) {
Ok(FloatOrInt(v))
} else {
ctx.emit_error(DecodeError::conversion(
val,
format!("value must be between {MIN} and {MAX}"),
));
Ok(FloatOrInt::default())
}
}
Err(e) => {
ctx.emit_error(DecodeError::conversion(val, e));
Ok(FloatOrInt::default())
}
},
_ => {
ctx.emit_error(DecodeError::unsupported(
val,
"Unsupported value, only numbers are recognized",
));
Ok(FloatOrInt::default())
}
}
}
}
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)]
2023-09-14 22:43:12 +04:00
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,
#[knuffel(child)]
pub keep_max_bpc_unchanged: bool,
#[knuffel(child)]
pub restrict_primary_scanout_to_matching_format: bool,
2024-01-06 09:14:48 +04:00
#[knuffel(child, unwrap(argument))]
pub render_drm_device: Option<PathBuf>,
#[knuffel(child)]
pub force_pipewire_invalid_modifier: bool,
#[knuffel(child)]
pub emulate_zero_presentation_time: bool,
2024-08-22 14:36:47 +03:00
#[knuffel(child)]
pub disable_resize_throttling: bool,
2024-08-22 14:44:11 +03:00
#[knuffel(child)]
pub disable_transactions: bool,
2024-11-05 09:40:54 +03:00
#[knuffel(child)]
pub keep_laptop_panel_on_when_lid_is_closed: bool,
2024-11-06 08:42:22 +03:00
#[knuffel(child)]
pub disable_monitor_names: bool,
#[knuffel(child)]
pub strict_new_window_focus_policy: bool,
#[knuffel(child)]
pub honor_xdg_activation_with_invalid_serial: bool,
#[knuffel(child)]
pub deactivate_unfocused_windows: bool,
#[knuffel(child)]
pub skip_cursor_only_updates_during_vrr: bool,
2023-09-06 15:49:46 +04:00
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PreviewRender {
Screencast,
ScreenCapture,
}
#[derive(Debug, Clone)]
pub enum ConfigPath {
/// Explicitly set config path.
///
/// Load the config only from this path, never create it.
Explicit(PathBuf),
/// Default config path.
///
/// Prioritize the user path, fallback to the system path, fallback to creating the user path
/// at compositor startup.
Regular {
/// User config path, usually `$XDG_CONFIG_HOME/niri/config.kdl`.
user_path: PathBuf,
/// System config path, usually `/etc/niri/config.kdl`.
system_path: PathBuf,
},
}
impl ConfigPath {
/// Load the config, or return an error if it doesn't exist.
pub fn load(&self) -> miette::Result<Config> {
let _span = tracy_client::span!("ConfigPath::load");
self.load_inner(|user_path, system_path| {
Err(miette::miette!(
"no config file found; create one at {user_path:?} or {system_path:?}",
))
})
.context("error loading config")
}
/// Load the config, or create it if it doesn't exist.
///
/// Returns a tuple containing the path that was created, if any, and the loaded config.
///
/// If the config was created, but for some reason could not be read afterwards,
/// this may return `(Some(_), Err(_))`.
pub fn load_or_create(&self) -> (Option<&Path>, miette::Result<Config>) {
let _span = tracy_client::span!("ConfigPath::load_or_create");
let mut created_at = None;
let result = self
.load_inner(|user_path, _| {
Self::create(user_path, &mut created_at)
.map(|()| user_path)
.with_context(|| format!("error creating config at {user_path:?}"))
})
.context("error loading config");
(created_at, result)
}
fn load_inner<'a>(
&'a self,
maybe_create: impl FnOnce(&'a Path, &'a Path) -> miette::Result<&'a Path>,
) -> miette::Result<Config> {
let path = match self {
ConfigPath::Explicit(path) => path.as_path(),
ConfigPath::Regular {
user_path,
system_path,
} => {
if user_path.exists() {
user_path.as_path()
} else if system_path.exists() {
system_path.as_path()
} else {
maybe_create(user_path.as_path(), system_path.as_path())?
}
}
};
Config::load(path)
2024-01-07 09:07:22 +04:00
}
fn create<'a>(path: &'a Path, created_at: &mut Option<&'a Path>) -> miette::Result<()> {
if let Some(default_parent) = path.parent() {
fs::create_dir_all(default_parent)
.into_diagnostic()
.with_context(|| format!("error creating config directory {default_parent:?}"))?;
}
// Create the config and fill it with the default config if it doesn't exist.
let mut new_file = match File::options()
.read(true)
.write(true)
.create_new(true)
.open(path)
{
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => return Ok(()),
res => res,
}
.into_diagnostic()
.with_context(|| format!("error opening config file at {path:?}"))?;
*created_at = Some(path);
let default = include_bytes!("../../resources/default-config.kdl");
new_file
.write_all(default)
.into_diagnostic()
.with_context(|| format!("error writing default config to {path:?}"))?;
Ok(())
}
}
impl Config {
pub fn load(path: &Path) -> miette::Result<Self> {
let contents = 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) {
2024-12-27 15:40:48 +03:00
if other.off {
self.off = true;
self.on = false;
}
if other.on {
self.off = false;
self.on = true;
}
2024-04-24 21:49:07 +04:00
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);
}
2025-03-22 19:04:24 +01:00
if let Some(x) = other.urgent_color {
self.urgent_color = Some(x);
}
2024-04-24 21:49:07 +04:00
if let Some(x) = other.active_gradient {
self.active_gradient = Some(x);
}
if let Some(x) = other.inactive_gradient {
self.inactive_gradient = Some(x);
}
2025-03-22 19:04:24 +01:00
if let Some(x) = other.urgent_gradient {
self.urgent_gradient = Some(x);
}
2024-04-24 21:49:07 +04:00
}
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;
}
2025-03-22 19:04:24 +01:00
if let Some(x) = self.urgent_color {
config.urgent_color = x;
config.urgent_gradient = None;
}
2024-04-24 21:49:07 +04:00
if let Some(x) = self.active_gradient {
config.active_gradient = Some(x);
}
if let Some(x) = self.inactive_gradient {
config.inactive_gradient = Some(x);
}
2025-03-22 19:04:24 +01:00
if let Some(x) = self.urgent_gradient {
config.urgent_gradient = Some(x);
}
2024-04-24 21:49:07 +04:00
config
}
}
2025-01-15 14:16:05 +03:00
impl ShadowRule {
pub fn merge_with(&mut self, other: &Self) {
if other.off {
self.off = true;
self.on = false;
}
if other.on {
self.off = false;
self.on = true;
}
if let Some(x) = other.offset {
self.offset = Some(x);
}
if let Some(x) = other.softness {
self.softness = Some(x);
}
if let Some(x) = other.spread {
self.spread = Some(x);
}
if let Some(x) = other.draw_behind_window {
self.draw_behind_window = Some(x);
}
if let Some(x) = other.color {
self.color = Some(x);
}
if let Some(x) = other.inactive_color {
self.inactive_color = Some(x);
}
}
pub fn resolve_against(&self, mut config: Shadow) -> Shadow {
config.on |= self.on;
if self.off {
config.on = false;
}
if let Some(x) = self.offset {
config.offset = x;
}
if let Some(x) = self.softness {
config.softness = x;
}
if let Some(x) = self.spread {
config.spread = x;
}
if let Some(x) = self.draw_behind_window {
config.draw_behind_window = x;
}
if let Some(x) = self.color {
config.color = x;
}
if let Some(x) = self.inactive_color {
config.inactive_color = Some(x);
}
config
}
}
2025-02-02 08:41:42 +03:00
impl TabIndicatorRule {
pub fn merge_with(&mut self, other: &Self) {
if let Some(x) = other.active_color {
self.active_color = Some(x);
}
if let Some(x) = other.inactive_color {
self.inactive_color = Some(x);
}
2025-05-10 22:34:53 +03:00
if let Some(x) = other.urgent_color {
self.urgent_color = Some(x);
}
2025-02-02 08:41:42 +03:00
if let Some(x) = other.active_gradient {
self.active_gradient = Some(x);
}
if let Some(x) = other.inactive_gradient {
self.inactive_gradient = Some(x);
}
2025-05-10 22:34:53 +03:00
if let Some(x) = other.urgent_gradient {
self.urgent_gradient = Some(x);
}
2025-02-02 08:41:42 +03:00
}
}
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 {
2025-03-09 18:55:17 +01:00
// Radius = 0 is preserved, so that square corners remain square.
2024-05-05 07:43:21 +04:00
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;
}
2025-03-09 18:55:17 +01:00
if width < 0. {
self.top_left = self.top_left.max(0.);
self.top_right = self.top_right.max(0.);
self.bottom_left = self.bottom_left.max(0.);
self.bottom_right = self.bottom_right.max(0.);
}
2024-05-05 07:43:21 +04:00
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,
}
}
}
impl FromStr for GradientInterpolation {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split_whitespace();
let in_part1 = iter.next();
let in_part2 = iter.next();
let in_part3 = iter.next();
let Some(in_part1) = in_part1 else {
return Err(miette!("missing color space"));
};
let color = match in_part1 {
"srgb" => GradientColorSpace::Srgb,
"srgb-linear" => GradientColorSpace::SrgbLinear,
"oklab" => GradientColorSpace::Oklab,
"oklch" => GradientColorSpace::Oklch,
x => {
return Err(miette!(
"invalid color space {x}; can be srgb, srgb-linear, oklab or oklch"
))
}
};
let interpolation = if let Some(in_part2) = in_part2 {
if color != GradientColorSpace::Oklch {
return Err(miette!("only oklch color space can have hue interpolation"));
}
if in_part3 != Some("hue") {
return Err(miette!(
"interpolation must end with \"hue\", like \"oklch shorter hue\""
));
} else if iter.next().is_some() {
return Err(miette!("unexpected text after hue interpolation"));
} else {
match in_part2 {
"shorter" => HueInterpolation::Shorter,
"longer" => HueInterpolation::Longer,
"increasing" => HueInterpolation::Increasing,
"decreasing" => HueInterpolation::Decreasing,
x => {
return Err(miette!(
"invalid hue interpolation {x}; \
can be shorter, longer, increasing, decreasing"
))
}
}
}
} else {
HueInterpolation::default()
};
Ok(Self {
color_space: color,
hue_interpolation: interpolation,
})
}
}
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 color = csscolorparser::parse(s)
.into_diagnostic()?
.clamp()
.to_array();
Ok(Self::from_array_unpremul(color))
2024-02-21 21:27:44 +04:00
}
}
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::from_array_unpremul([r, g, b, a].map(|x| x as f32 / 255.))
2024-02-26 09:02:47 +04:00
}
}
// 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",
))
}
}
2024-07-04 17:51:11 +04:00
impl FromIterator<Output> for Outputs {
fn from_iter<T: IntoIterator<Item = Output>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}
impl Outputs {
pub fn find(&self, name: &OutputName) -> Option<&Output> {
self.0.iter().find(|o| name.matches(&o.name))
2024-07-04 17:51:11 +04:00
}
pub fn find_mut(&mut self, name: &OutputName) -> Option<&mut Output> {
self.0.iter_mut().find(|o| name.matches(&o.name))
}
}
impl OutputName {
pub fn from_ipc_output(output: &niri_ipc::Output) -> Self {
Self {
connector: output.name.clone(),
make: (output.make != "Unknown").then(|| output.make.clone()),
model: (output.model != "Unknown").then(|| output.model.clone()),
serial: output.serial.clone(),
}
}
/// Returns an output description matching what Smithay's `Output::new()` does.
pub fn format_description(&self) -> String {
format!(
"{} - {} - {}",
self.make.as_deref().unwrap_or("Unknown"),
self.model.as_deref().unwrap_or("Unknown"),
self.connector,
)
}
/// Returns an output name that will match by make/model/serial or, if they are missing, by
/// connector.
pub fn format_make_model_serial_or_connector(&self) -> String {
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
self.connector.to_string()
} else {
2024-11-08 09:10:54 +03:00
self.format_make_model_serial()
}
}
2024-11-08 09:10:54 +03:00
pub fn format_make_model_serial(&self) -> String {
let make = self.make.as_deref().unwrap_or("Unknown");
let model = self.model.as_deref().unwrap_or("Unknown");
let serial = self.serial.as_deref().unwrap_or("Unknown");
format!("{make} {model} {serial}")
}
pub fn matches(&self, target: &str) -> bool {
// Match by connector.
if target.eq_ignore_ascii_case(&self.connector) {
return true;
}
// If no other fields are available, don't try to match by them.
//
// This is used by niri msg output.
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
return false;
}
// Match by "make model serial" with Unknown if something is missing.
let make = self.make.as_deref().unwrap_or("Unknown");
let model = self.model.as_deref().unwrap_or("Unknown");
let serial = self.serial.as_deref().unwrap_or("Unknown");
let Some(target_make) = target.get(..make.len()) else {
return false;
};
let rest = &target[make.len()..];
if !target_make.eq_ignore_ascii_case(make) {
return false;
}
if !rest.starts_with(' ') {
return false;
}
let rest = &rest[1..];
let Some(target_model) = rest.get(..model.len()) else {
return false;
};
let rest = &rest[model.len()..];
if !target_model.eq_ignore_ascii_case(model) {
return false;
}
if !rest.starts_with(' ') {
return false;
}
let rest = &rest[1..];
if !rest.eq_ignore_ascii_case(serial) {
return false;
}
true
2024-07-04 17:51:11 +04:00
}
// Similar in spirit to Ord, but I don't want to derive Eq to avoid mistakes (you should use
// `Self::match`, not Eq).
pub fn compare(&self, other: &Self) -> std::cmp::Ordering {
let self_missing_mms = self.make.is_none() && self.model.is_none() && self.serial.is_none();
let other_missing_mms =
other.make.is_none() && other.model.is_none() && other.serial.is_none();
match (self_missing_mms, other_missing_mms) {
(true, true) => self.connector.cmp(&other.connector),
(true, false) => std::cmp::Ordering::Greater,
(false, true) => std::cmp::Ordering::Less,
(false, false) => self
.make
.cmp(&other.make)
.then_with(|| self.model.cmp(&other.model))
.then_with(|| self.serial.cmp(&other.serial))
.then_with(|| self.connector.cmp(&other.connector)),
}
}
2024-07-04 17:51:11 +04:00
}
2024-09-05 23:37:10 +02:00
impl<S> knuffel::Decode<S> for DefaultPresetSize
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",
));
}
2024-09-05 23:37:10 +02:00
PresetSize::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
}
}
2024-05-11 22:40:30 +02:00
impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
fn type_check(
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
ctx: &mut knuffel::decode::Context<S>,
) {
if let Some(type_name) = &type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
}
fn raw_decode(
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<WorkspaceName, DecodeError<S>> {
#[derive(Debug)]
2024-05-16 10:54:24 +04:00
struct WorkspaceNameSet(Vec<String>);
2024-05-11 22:40:30 +02:00
match &**val {
knuffel::ast::Literal::String(ref s) => {
2024-05-16 10:54:24 +04:00
let mut name_set: Vec<String> = match ctx.get::<WorkspaceNameSet>() {
2024-05-11 22:40:30 +02:00
Some(h) => h.0.clone(),
2024-05-16 10:54:24 +04:00
None => Vec::new(),
2024-05-11 22:40:30 +02:00
};
2024-05-16 10:54:24 +04:00
if name_set.iter().any(|name| name.eq_ignore_ascii_case(s)) {
2024-05-11 22:40:30 +02:00
ctx.emit_error(DecodeError::unexpected(
val,
"named workspace",
2025-07-13 12:49:00 +03:00
format!("duplicate named workspace: {s}"),
2024-05-11 22:40:30 +02:00
));
return Ok(Self(String::new()));
}
2024-05-16 10:54:24 +04:00
name_set.push(s.to_string());
2024-05-11 22:40:30 +02:00
ctx.set(WorkspaceNameSet(name_set));
Ok(Self(s.clone().into()))
}
_ => {
ctx.emit_error(DecodeError::unsupported(
val,
"workspace names must be strings",
));
Ok(Self(String::new()))
}
}
}
}
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>> {
2024-05-15 19:38:29 +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 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().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 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
}
}
2024-07-08 11:24:08 +04:00
impl<S> knuffel::Decode<S> for ScreenshotUiOpenAnim
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;
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
}
}
2025-04-25 09:36:50 +03:00
impl<S> knuffel::Decode<S> for OverviewOpenCloseAnim
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;
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
}
}
2024-04-17 14:06:32 +04:00
impl Animation {
pub fn new_off() -> Self {
Self {
off: true,
kind: AnimationKind::Easing(EasingParams {
duration_ms: 0,
curve: AnimationCurve::Linear,
}),
}
}
2024-04-17 14:06:32 +04:00
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-06-30 22:37:44 +05:00
let mut repeat = true;
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;
let mut allow_inhibiting = true;
2025-02-13 08:45:23 +03:00
let mut hotkey_overlay_title = None;
2024-03-22 20:47:40 +04:00
for (name, val) in &node.properties {
match &***name {
2024-06-30 22:37:44 +05:00
"repeat" => {
repeat = knuffel::traits::DecodeScalar::decode(val, ctx)?;
}
2024-03-22 20:47:40 +04:00
"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);
}
"allow-inhibiting" => {
allow_inhibiting = knuffel::traits::DecodeScalar::decode(val, ctx)?;
}
2025-02-13 08:45:23 +03:00
"hotkey-overlay-title" => {
hotkey_overlay_title = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
}
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-06-30 22:37:44 +05:00
repeat: true,
2024-03-22 20:47:40 +04:00
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
2025-02-13 08:45:23 +03:00
hotkey_overlay_title: None,
};
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) => {
2025-08-20 14:31:34 +03:00
if !matches!(action, Action::Spawn(_) | Action::SpawnSh(_)) {
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",
));
}
}
// The toggle-inhibit action must always be uninhibitable.
// Otherwise, it would be impossible to trigger it.
if matches!(action, Action::ToggleKeyboardShortcutsInhibit) {
allow_inhibiting = false;
}
Ok(Self {
key,
action,
2024-06-30 22:37:44 +05:00
repeat,
cooldown,
allow_when_locked,
allow_inhibiting,
2025-02-13 08:45:23 +03:00
hotkey_overlay_title,
})
}
Err(e) => {
ctx.emit_error(e);
Ok(dummy)
}
}
} else {
ctx.emit_error(DecodeError::missing(
node,
"expected an action for this keybind",
));
Ok(dummy)
}
}
}
2025-02-05 09:34:25 -05:00
impl FromStr for ModKey {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match &*s.to_ascii_lowercase() {
"ctrl" | "control" => Ok(Self::Ctrl),
"shift" => Ok(Self::Shift),
"alt" => Ok(Self::Alt),
"super" | "win" => Ok(Self::Super),
"iso_level3_shift" | "mod5" => Ok(Self::IsoLevel3Shift),
"iso_level5_shift" | "mod3" => Ok(Self::IsoLevel5Shift),
_ => Err(miette!("invalid Mod key: {s}")),
}
}
}
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;
2024-07-31 11:00:35 -04:00
} else if part.eq_ignore_ascii_case("iso_level5_shift")
|| part.eq_ignore_ascii_case("mod3")
{
modifiers |= Modifiers::ISO_LEVEL5_SHIFT;
2023-09-05 12:58:51 +04:00
} else {
return Err(miette!("invalid modifier: {part}"));
}
}
let trigger = if key.eq_ignore_ascii_case("MouseLeft") {
Trigger::MouseLeft
} else if key.eq_ignore_ascii_case("MouseRight") {
Trigger::MouseRight
} else if key.eq_ignore_ascii_case("MouseMiddle") {
Trigger::MouseMiddle
} else if key.eq_ignore_ascii_case("MouseBack") {
Trigger::MouseBack
} else if key.eq_ignore_ascii_case("MouseForward") {
Trigger::MouseForward
} else 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""#
)),
}
}
}
impl FromStr for ScrollMethod {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"no-scroll" => Ok(Self::NoScroll),
"two-finger" => Ok(Self::TwoFinger),
"edge" => Ok(Self::Edge),
"on-button-down" => Ok(Self::OnButtonDown),
_ => Err(miette!(
r#"invalid scroll method, can be "no-scroll", "two-finger", "edge", or "on-button-down""#
)),
}
}
}
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""#
)),
}
}
}
impl FromStr for Percent {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((value, empty)) = s.split_once('%') else {
return Err(miette!("value must end with '%'"));
};
if !empty.is_empty() {
return Err(miette!("trailing characters after '%' are not allowed"));
}
let value: f64 = value.parse().map_err(|_| miette!("error parsing value"))?;
Ok(Percent(value / 100.))
}
}
2023-09-05 12:58:51 +04:00
#[cfg(test)]
mod tests {
use insta::{assert_debug_snapshot, assert_snapshot};
2024-12-28 11:40:16 +03:00
use niri_ipc::PositionChange;
2024-06-11 09:10:46 +03:00
use pretty_assertions::assert_eq;
2023-09-05 12:58:51 +04:00
use super::*;
#[track_caller]
fn do_parse(text: &str) -> Config {
Config::parse("test.kdl", text)
2023-09-05 12:58:51 +04:00
.map_err(miette::Report::new)
.unwrap()
2023-09-05 12:58:51 +04:00
}
#[test]
fn parse_scroll_factor_combined() {
// Test combined scroll-factor syntax
let parsed = do_parse(
r#"
input {
mouse {
scroll-factor 2.0
}
touchpad {
scroll-factor 1.5
}
}
"#,
);
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
Some(
ScrollFactor {
base: Some(
FloatOrInt(
2.0,
),
),
horizontal: None,
vertical: None,
},
)
"#);
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
Some(
ScrollFactor {
base: Some(
FloatOrInt(
1.5,
),
),
horizontal: None,
vertical: None,
},
)
"#);
}
#[test]
fn parse_scroll_factor_split() {
// Test split horizontal/vertical syntax
let parsed = do_parse(
r#"
input {
mouse {
scroll-factor horizontal=2.0 vertical=-1.0
}
touchpad {
scroll-factor horizontal=-1.5 vertical=0.5
}
}
"#,
);
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
Some(
ScrollFactor {
base: None,
horizontal: Some(
FloatOrInt(
2.0,
),
),
vertical: Some(
FloatOrInt(
-1.0,
),
),
},
)
"#);
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
Some(
ScrollFactor {
base: None,
horizontal: Some(
FloatOrInt(
-1.5,
),
),
vertical: Some(
FloatOrInt(
0.5,
),
),
},
)
"#);
}
#[test]
fn parse_scroll_factor_partial() {
// Test partial specification (only one axis)
let parsed = do_parse(
r#"
input {
mouse {
scroll-factor horizontal=2.0
}
touchpad {
scroll-factor vertical=-1.5
}
}
"#,
);
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
Some(
ScrollFactor {
base: None,
horizontal: Some(
FloatOrInt(
2.0,
),
),
vertical: None,
},
)
"#);
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
Some(
ScrollFactor {
base: None,
horizontal: None,
vertical: Some(
FloatOrInt(
-1.5,
),
),
},
)
"#);
}
#[test]
fn parse_scroll_factor_mixed() {
// Test mixed base + override syntax
let parsed = do_parse(
r#"
input {
mouse {
scroll-factor 2 vertical=-1
}
touchpad {
scroll-factor 1.5 horizontal=3
}
}
"#,
);
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
Some(
ScrollFactor {
base: Some(
FloatOrInt(
2.0,
),
),
horizontal: None,
vertical: Some(
FloatOrInt(
-1.0,
),
),
},
)
"#);
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
Some(
ScrollFactor {
base: Some(
FloatOrInt(
1.5,
),
),
horizontal: Some(
FloatOrInt(
3.0,
),
),
vertical: None,
},
)
"#);
}
#[test]
fn scroll_factor_h_v_factors() {
let sf = ScrollFactor {
base: Some(FloatOrInt(2.0)),
horizontal: None,
vertical: None,
};
assert_debug_snapshot!(sf.h_v_factors(), @r#"
(
2.0,
2.0,
)
"#);
let sf = ScrollFactor {
base: None,
horizontal: Some(FloatOrInt(3.0)),
vertical: Some(FloatOrInt(-1.0)),
};
assert_debug_snapshot!(sf.h_v_factors(), @r#"
(
3.0,
-1.0,
)
"#);
let sf = ScrollFactor {
base: Some(FloatOrInt(2.0)),
horizontal: Some(FloatOrInt(1.0)),
vertical: None,
};
assert_debug_snapshot!(sf.h_v_factors(), @r"
(
1.0,
2.0,
)
");
}
2023-09-05 12:58:51 +04:00
#[test]
fn parse() {
let parsed = do_parse(
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
2025-03-01 23:01:34 -08:00
drag true
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"
scroll-method "two-finger"
scroll-button 272
scroll-button-lock
2024-01-08 10:32:04 +04:00
tap-button-map "left-middle-right"
disabled-on-external-mouse
scroll-factor 0.9
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"
scroll-method "no-scroll"
scroll-button 273
2024-07-13 07:34:22 +03:00
middle-emulation
scroll-factor 0.2
2024-01-08 11:53:34 +04:00
}
trackpoint {
off
natural-scroll
accel-speed 0.0
accel-profile "flat"
scroll-method "on-button-down"
scroll-button 274
}
trackball {
off
natural-scroll
accel-speed 0.0
accel-profile "flat"
scroll-method "edge"
scroll-button 275
scroll-button-lock
left-handed
middle-emulation
}
2023-10-03 17:02:07 +04:00
tablet {
map-to-output "eDP-1"
calibration-matrix 1.0 2.0 3.0 \
4.0 5.0 6.0
2023-10-03 17:02:07 +04:00
}
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
2025-02-05 09:34:25 -05:00
mod-key "Mod5"
mod-key-nested "Super"
2023-09-05 12:58:51 +04:00
}
output "eDP-1" {
focus-at-startup
2024-06-18 11:01:18 +03:00
scale 2
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-08-22 18:58:07 +10:00
variable-refresh-rate on-demand=true
2024-07-25 23:41:33 +05:30
background-color "rgba(25, 25, 102, 1.0)"
}
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
2025-01-15 14:16:05 +03:00
shadow {
offset x=10 y=-20
}
2025-02-02 08:41:42 +03:00
tab-indicator {
width 10
2025-02-05 10:36:46 +03:00
position "top"
2025-02-02 08:41:42 +03:00
}
preset-column-widths {
proportion 0.25
proportion 0.5
fixed 960
fixed 1280
}
2024-09-05 23:37:10 +02:00
preset-window-heights {
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"
2024-07-15 15:51:48 +02:00
2025-02-01 10:46:52 +03:00
default-column-display "tabbed"
2024-07-15 15:51:48 +02:00
insert-hint {
color "rgb(255, 200, 127)"
2024-11-02 09:33:44 +03:00
gradient from="rgba(10, 20, 30, 1.0)" to="#0080ffff" relative-to="workspace-view"
2024-07-15 15:51:48 +02:00
}
}
spawn-at-startup "alacritty" "-e" "fish"
2025-08-20 14:31:34 +03:00
spawn-at-startup-sh "qs -c ~/source/qs/MyAwesomeShell"
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
hide-when-typing
hide-after-inactive-ms 3000
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
2025-01-22 00:00:35 -05:00
clipboard {
disable-primary
}
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
}
2025-02-16 08:46:38 +03:00
gestures {
dnd-edge-view-scroll {
trigger-width 10
max-speed 50
}
}
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-11-29 21:11:02 +03:00
open-floating false
2024-12-27 09:31:32 +03:00
open-focused true
2024-12-27 09:58:22 +03:00
default-window-height { fixed 500; }
2025-02-06 09:09:07 +03:00
default-column-display "tabbed"
default-floating-position x=100 y=-200 relative-to="bottom-left"
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
2024-06-17 09:16:28 +03:00
width 8.5
2024-04-24 21:49:07 +04:00
}
2025-02-02 08:41:42 +03:00
tab-indicator {
active-color "#f00"
}
2024-02-13 17:46:37 +04:00
}
layer-rule {
match namespace="^notifications$"
block-out-from "screencast"
}
2023-09-05 12:58:51 +04:00
binds {
2025-02-13 08:45:23 +03:00
Mod+Escape hotkey-overlay-title="Inhibit" { toggle-keyboard-shortcuts-inhibit; }
Mod+Shift+Escape allow-inhibiting=true { toggle-keyboard-shortcuts-inhibit; }
Mod+T allow-when-locked=true { spawn "alacritty"; }
2025-02-13 08:45:23 +03:00
Mod+Q hotkey-overlay-title=null { close-window; }
2023-09-05 12:58:51 +04:00
Mod+Shift+H { focus-monitor-left; }
Mod+Shift+O { focus-monitor "eDP-1"; }
2023-09-05 12:58:51 +04:00
Mod+Ctrl+Shift+L { move-window-to-monitor-right; }
Mod+Ctrl+Alt+O { move-window-to-monitor "eDP-1"; }
Mod+Ctrl+Alt+P { move-column-to-monitor "DP-1"; }
2023-09-05 12:58:51 +04:00
Mod+Comma { consume-window-into-column; }
2024-02-12 07:48:31 +04:00
Mod+1 { focus-workspace 1; }
2024-05-11 22:40:30 +02:00
Mod+Shift+1 { focus-workspace "workspace-1"; }
Mod+Shift+E allow-inhibiting=false { quit skip-confirmation=true; }
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
2025-08-20 14:31:34 +03:00
Super+Alt+S allow-when-locked=true { spawn-sh "pkill orca || exec orca"; }
2023-09-05 12:58:51 +04:00
}
2023-09-06 15:49:46 +04:00
2024-10-18 16:00:40 +02:00
switch-events {
tablet-mode-on { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true"; }
tablet-mode-off { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false"; }
}
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-05-11 22:40:30 +02:00
workspace "workspace-1" {
open-on-output "eDP-1"
}
workspace "workspace-2"
workspace "workspace-3"
2024-02-21 21:27:44 +04:00
"##,
);
assert_debug_snapshot!(parsed, @r#"
2025-02-16 08:46:38 +03:00
Config {
input: Input {
keyboard: Keyboard {
xkb: Xkb {
rules: "",
model: "",
layout: "us,ru",
variant: "",
options: Some(
"grp:win_space_toggle",
),
2025-02-16 08:46:38 +03:00
file: None,
},
repeat_delay: 600,
repeat_rate: 25,
track_layout: Window,
numlock: false,
2025-02-16 08:46:38 +03:00
},
touchpad: Touchpad {
off: false,
tap: true,
dwt: true,
dwtp: true,
2025-03-01 23:01:34 -08:00
drag: Some(
true,
),
2025-02-16 08:46:38 +03:00
drag_lock: false,
natural_scroll: false,
click_method: Some(
Clickfinger,
),
2025-06-09 16:13:31 +03:00
accel_speed: FloatOrInt(
0.2,
),
2025-02-16 08:46:38 +03:00
accel_profile: Some(
Flat,
),
scroll_method: Some(
TwoFinger,
),
scroll_button: Some(
272,
),
scroll_button_lock: true,
2025-02-16 08:46:38 +03:00
tap_button_map: Some(
LeftMiddleRight,
),
left_handed: false,
disabled_on_external_mouse: true,
middle_emulation: false,
scroll_factor: Some(
ScrollFactor {
base: Some(
FloatOrInt(
0.9,
),
),
horizontal: None,
vertical: None,
},
2025-02-16 08:46:38 +03:00
),
},
mouse: Mouse {
off: false,
natural_scroll: true,
2025-06-09 16:13:31 +03:00
accel_speed: FloatOrInt(
0.4,
),
2025-02-16 08:46:38 +03:00
accel_profile: Some(
Flat,
),
scroll_method: Some(
NoScroll,
),
scroll_button: Some(
273,
),
scroll_button_lock: false,
2025-02-16 08:46:38 +03:00
left_handed: false,
middle_emulation: true,
scroll_factor: Some(
ScrollFactor {
base: Some(
FloatOrInt(
0.2,
),
),
horizontal: None,
vertical: None,
},
2025-02-16 08:46:38 +03:00
),
},
trackpoint: Trackpoint {
off: true,
natural_scroll: true,
2025-06-09 16:13:31 +03:00
accel_speed: FloatOrInt(
0.0,
),
2025-02-16 08:46:38 +03:00
accel_profile: Some(
Flat,
),
scroll_method: Some(
OnButtonDown,
),
scroll_button: Some(
274,
),
scroll_button_lock: false,
left_handed: false,
2025-02-16 08:46:38 +03:00
middle_emulation: false,
},
trackball: Trackball {
off: true,
natural_scroll: true,
2025-06-09 16:13:31 +03:00
accel_speed: FloatOrInt(
0.0,
),
2025-02-16 08:46:38 +03:00
accel_profile: Some(
Flat,
),
scroll_method: Some(
Edge,
),
scroll_button: Some(
275,
),
scroll_button_lock: true,
2025-02-16 08:46:38 +03:00
left_handed: true,
middle_emulation: true,
},
tablet: Tablet {
off: false,
calibration_matrix: Some(
[
1.0,
2.0,
3.0,
4.0,
5.0,
6.0,
],
),
map_to_output: Some(
"eDP-1",
),
left_handed: false,
},
touch: Touch {
2025-02-21 13:35:10 -05:00
off: false,
2025-02-16 08:46:38 +03:00
map_to_output: Some(
"eDP-1",
),
},
disable_power_key_handling: true,
warp_mouse_to_focus: Some(
WarpMouseToFocus {
mode: None,
},
),
2025-02-16 08:46:38 +03:00
focus_follows_mouse: Some(
FocusFollowsMouse {
max_scroll_amount: None,
2023-09-05 12:58:51 +04:00
},
2025-02-16 08:46:38 +03:00
),
workspace_auto_back_and_forth: true,
2025-02-05 09:34:25 -05:00
mod_key: Some(
IsoLevel3Shift,
),
mod_key_nested: Some(
Super,
),
2025-02-16 08:46:38 +03:00
},
outputs: Outputs(
[
Output {
off: false,
2025-02-16 08:46:38 +03:00
name: "eDP-1",
scale: Some(
FloatOrInt(
2025-02-16 08:46:38 +03:00
2.0,
),
),
2025-02-16 08:46:38 +03:00
transform: Flipped90,
position: Some(
Position {
x: 10,
y: 20,
},
),
2025-02-16 08:46:38 +03:00
mode: Some(
ConfiguredMode {
width: 1920,
height: 1080,
refresh: Some(
144.0,
),
},
),
2025-02-16 08:46:38 +03:00
variable_refresh_rate: Some(
Vrr {
on_demand: true,
},
),
focus_at_startup: true,
2025-05-06 17:12:07 +03:00
background_color: Some(
Color {
r: 0.09803922,
g: 0.09803922,
b: 0.4,
a: 1.0,
},
),
2025-04-28 07:53:03 +03:00
backdrop_color: None,
},
2025-02-16 08:46:38 +03:00
],
),
spawn_at_startup: [
SpawnAtStartup {
command: [
"alacritty",
"-e",
"fish",
],
},
],
2025-08-20 14:31:34 +03:00
spawn_at_startup_sh: [
SpawnAtStartupSh {
command: "qs -c ~/source/qs/MyAwesomeShell",
},
],
2025-02-16 08:46:38 +03:00
layout: Layout {
focus_ring: FocusRing {
off: false,
width: FloatOrInt(
5.0,
),
active_color: Color {
r: 0.0,
g: 0.39215687,
b: 0.78431374,
a: 1.0,
},
2025-02-16 08:46:38 +03:00
inactive_color: Color {
r: 1.0,
g: 0.78431374,
b: 0.39215687,
a: 0.0,
},
2025-03-22 19:04:24 +01:00
urgent_color: Color {
r: 0.60784316,
g: 0.0,
b: 0.0,
a: 1.0,
},
2025-02-16 08:46:38 +03:00
active_gradient: Some(
Gradient {
from: Color {
r: 0.039215688,
g: 0.078431375,
b: 0.11764706,
a: 1.0,
},
to: Color {
r: 0.0,
g: 0.5019608,
b: 1.0,
a: 1.0,
},
angle: 180,
relative_to: WorkspaceView,
in_: GradientInterpolation {
color_space: Srgb,
hue_interpolation: Shorter,
},
},
),
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_gradient: None,
2025-02-16 08:46:38 +03:00
},
border: Border {
off: false,
width: FloatOrInt(
3.0,
),
active_color: Color {
r: 1.0,
g: 0.78431374,
b: 0.49803922,
a: 1.0,
},
inactive_color: Color {
r: 1.0,
g: 0.78431374,
b: 0.39215687,
a: 0.0,
},
2025-03-22 19:04:24 +01:00
urgent_color: Color {
r: 0.60784316,
g: 0.0,
b: 0.0,
a: 1.0,
},
2025-02-16 08:46:38 +03:00
active_gradient: None,
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_gradient: None,
2025-02-16 08:46:38 +03:00
},
shadow: Shadow {
on: false,
offset: ShadowOffset {
x: FloatOrInt(
10.0,
),
2025-02-16 08:46:38 +03:00
y: FloatOrInt(
-20.0,
),
2023-10-03 17:02:07 +04:00
},
2025-02-16 08:46:38 +03:00
softness: FloatOrInt(
30.0,
),
spread: FloatOrInt(
5.0,
),
draw_behind_window: false,
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.4392157,
},
inactive_color: None,
},
tab_indicator: TabIndicator {
off: false,
hide_when_single_tab: false,
place_within_column: false,
gap: FloatOrInt(
5.0,
),
width: FloatOrInt(
10.0,
),
length: TabIndicatorLength {
total_proportion: Some(
0.5,
),
2024-02-24 18:32:13 +01:00
},
2025-02-16 08:46:38 +03:00
position: Top,
gaps_between_tabs: FloatOrInt(
0.0,
),
corner_radius: FloatOrInt(
0.0,
),
2025-02-16 08:46:38 +03:00
active_color: None,
inactive_color: None,
2025-05-10 22:34:53 +03:00
urgent_color: None,
2025-02-16 08:46:38 +03:00
active_gradient: None,
inactive_gradient: None,
2025-05-10 22:34:53 +03:00
urgent_gradient: None,
2023-09-05 12:58:51 +04:00
},
2025-02-16 08:46:38 +03:00
insert_hint: InsertHint {
off: false,
color: Color {
r: 1.0,
g: 0.78431374,
b: 0.49803922,
a: 1.0,
},
gradient: Some(
Gradient {
from: Color {
r: 0.039215688,
g: 0.078431375,
b: 0.11764706,
a: 1.0,
},
to: Color {
r: 0.0,
g: 0.5019608,
b: 1.0,
a: 1.0,
},
2025-02-16 08:46:38 +03:00
angle: 180,
relative_to: WorkspaceView,
in_: GradientInterpolation {
color_space: Srgb,
hue_interpolation: Shorter,
},
},
2025-02-16 08:46:38 +03:00
),
},
preset_column_widths: [
Proportion(
0.25,
),
Proportion(
0.5,
),
Fixed(
960,
),
Fixed(
1280,
),
],
default_column_width: Some(
DefaultPresetSize(
Some(
Proportion(
0.25,
),
),
),
),
2025-02-16 08:46:38 +03:00
preset_window_heights: [
Proportion(
0.25,
),
Proportion(
0.5,
),
Fixed(
960,
),
Fixed(
1280,
),
],
2025-02-16 08:46:38 +03:00
center_focused_column: OnOverflow,
always_center_single_column: false,
empty_workspace_above_first: false,
default_column_display: Tabbed,
gaps: FloatOrInt(
8.0,
),
struts: Struts {
left: FloatOrInt(
1.0,
),
right: FloatOrInt(
2.0,
),
top: FloatOrInt(
3.0,
),
bottom: FloatOrInt(
0.0,
),
},
2025-05-06 17:12:07 +03:00
background_color: Color {
r: 0.25,
g: 0.25,
b: 0.25,
a: 1.0,
},
2025-02-16 08:46:38 +03:00
},
prefer_no_csd: true,
cursor: Cursor {
xcursor_theme: "breeze_cursors",
xcursor_size: 16,
hide_when_typing: true,
hide_after_inactive_ms: Some(
3000,
),
},
screenshot_path: Some(
"~/Screenshots/screenshot.png",
),
clipboard: Clipboard {
disable_primary: true,
},
hotkey_overlay: HotkeyOverlay {
skip_at_startup: true,
hide_not_bound: false,
2025-02-16 08:46:38 +03:00
},
config_notification: ConfigNotification {
disable_failed: false,
},
2025-02-16 08:46:38 +03:00
animations: Animations {
off: false,
2025-06-09 16:13:31 +03:00
slowdown: FloatOrInt(
2.0,
),
2025-02-16 08:46:38 +03:00
workspace_switch: WorkspaceSwitchAnim(
Animation {
off: false,
2025-02-16 08:46:38 +03:00
kind: Spring(
SpringParams {
damping_ratio: 1.0,
stiffness: 1000,
epsilon: 0.0001,
},
),
2025-02-16 08:46:38 +03:00
},
),
window_open: WindowOpenAnim {
anim: Animation {
off: true,
kind: Easing(
EasingParams {
duration_ms: 150,
curve: EaseOutExpo,
},
),
2023-09-26 13:09:33 +04:00
},
2025-02-16 08:46:38 +03:00
custom_shader: None,
},
window_close: WindowCloseAnim {
anim: Animation {
off: false,
2025-02-16 08:46:38 +03:00
kind: Easing(
EasingParams {
duration_ms: 150,
curve: EaseOutQuad,
},
),
},
2025-02-16 08:46:38 +03:00
custom_shader: None,
},
horizontal_view_movement: HorizontalViewMovementAnim(
Animation {
off: false,
kind: Easing(
EasingParams {
duration_ms: 100,
curve: EaseOutExpo,
},
),
2025-01-15 14:16:05 +03:00
},
2025-02-16 08:46:38 +03:00
),
window_movement: WindowMovementAnim(
Animation {
off: false,
2025-02-16 08:46:38 +03:00
kind: Spring(
SpringParams {
damping_ratio: 1.0,
stiffness: 800,
epsilon: 0.0001,
},
),
2025-02-02 08:41:42 +03:00
},
2025-02-16 08:46:38 +03:00
),
window_resize: WindowResizeAnim {
anim: Animation {
2024-07-15 15:51:48 +02:00
off: false,
2025-02-16 08:46:38 +03:00
kind: Spring(
SpringParams {
damping_ratio: 1.0,
stiffness: 800,
epsilon: 0.0001,
2024-11-02 09:33:44 +03:00
},
),
2024-07-15 15:51:48 +02:00
},
2025-02-16 08:46:38 +03:00
custom_shader: None,
},
config_notification_open_close: ConfigNotificationOpenCloseAnim(
Animation {
off: false,
kind: Spring(
SpringParams {
damping_ratio: 0.6,
stiffness: 1000,
epsilon: 0.001,
},
),
2025-02-16 08:46:38 +03:00
},
),
screenshot_ui_open: ScreenshotUiOpenAnim(
Animation {
off: false,
kind: Easing(
EasingParams {
duration_ms: 200,
curve: EaseOutQuad,
},
),
2025-02-16 08:46:38 +03:00
},
),
2025-04-25 09:36:50 +03:00
overview_open_close: OverviewOpenCloseAnim(
Animation {
off: false,
kind: Spring(
SpringParams {
damping_ratio: 1.0,
stiffness: 800,
epsilon: 0.0001,
},
),
},
),
2025-02-16 08:46:38 +03:00
},
2025-02-16 08:46:38 +03:00
gestures: Gestures {
dnd_edge_view_scroll: DndEdgeViewScroll {
trigger_width: FloatOrInt(
10.0,
),
2025-02-19 07:49:09 +03:00
delay_ms: 100,
2025-02-16 08:46:38 +03:00
max_speed: FloatOrInt(
50.0,
),
},
2025-04-25 10:02:31 +03:00
dnd_edge_workspace_switch: DndEdgeWorkspaceSwitch {
trigger_height: FloatOrInt(
50.0,
),
delay_ms: 100,
max_speed: FloatOrInt(
1500.0,
),
},
hot_corners: HotCorners {
off: false,
},
2025-02-16 08:46:38 +03:00
},
2025-04-25 09:36:50 +03:00
overview: Overview {
zoom: FloatOrInt(
0.5,
),
2025-04-28 07:53:03 +03:00
backdrop_color: Color {
r: 0.15,
g: 0.15,
b: 0.15,
a: 1.0,
},
workspace_shadow: WorkspaceShadow {
off: false,
offset: ShadowOffset {
x: FloatOrInt(
0.0,
),
y: FloatOrInt(
10.0,
),
},
softness: FloatOrInt(
40.0,
),
spread: FloatOrInt(
10.0,
),
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.3137255,
},
},
2025-04-25 09:36:50 +03:00
},
2025-02-16 08:46:38 +03:00
environment: Environment(
[
EnvironmentVariable {
name: "QT_QPA_PLATFORM",
value: Some(
"wayland",
),
2025-02-16 08:46:38 +03:00
},
EnvironmentVariable {
name: "DISPLAY",
value: None,
},
],
),
2025-06-04 08:26:51 +03:00
xwayland_satellite: XwaylandSatellite {
off: false,
path: "xwayland-satellite",
},
2025-02-16 08:46:38 +03:00
window_rules: [
WindowRule {
matches: [
Match {
app_id: Some(
RegexEq(
Regex(
".*alacritty",
),
),
),
title: None,
is_active: None,
is_focused: None,
is_active_in_column: None,
is_floating: None,
is_window_cast_target: None,
2025-05-10 22:48:11 +03:00
is_urgent: None,
2025-02-16 08:46:38 +03:00
at_startup: None,
},
],
2025-02-16 08:46:38 +03:00
excludes: [
Match {
app_id: None,
title: Some(
RegexEq(
Regex(
"~",
),
),
),
is_active: None,
is_focused: None,
is_active_in_column: None,
is_floating: None,
is_window_cast_target: None,
2025-05-10 22:48:11 +03:00
is_urgent: None,
2025-02-16 08:46:38 +03:00
at_startup: None,
},
Match {
app_id: None,
title: None,
is_active: Some(
true,
),
is_focused: Some(
false,
),
is_active_in_column: None,
is_floating: None,
is_window_cast_target: None,
2025-05-10 22:48:11 +03:00
is_urgent: None,
2025-02-16 08:46:38 +03:00
at_startup: None,
},
],
default_column_width: None,
default_window_height: Some(
DefaultPresetSize(
Some(
2025-02-16 08:46:38 +03:00
Fixed(
500,
),
),
),
),
2025-02-16 08:46:38 +03:00
open_on_output: Some(
"eDP-1",
),
2025-02-16 08:46:38 +03:00
open_on_workspace: None,
open_maximized: Some(
true,
),
open_fullscreen: Some(
false,
),
open_floating: Some(
false,
),
open_focused: Some(
true,
),
min_width: None,
min_height: None,
max_width: None,
max_height: None,
focus_ring: BorderRule {
off: true,
on: false,
width: Some(
FloatOrInt(
3.0,
),
),
2025-02-16 08:46:38 +03:00
active_color: None,
inactive_color: None,
2025-03-22 19:04:24 +01:00
urgent_color: None,
2025-02-16 08:46:38 +03:00
active_gradient: None,
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_gradient: None,
2025-02-16 08:46:38 +03:00
},
border: BorderRule {
off: false,
on: true,
width: Some(
FloatOrInt(
8.5,
),
),
2025-02-16 08:46:38 +03:00
active_color: None,
inactive_color: None,
2025-03-22 19:04:24 +01:00
urgent_color: None,
2025-02-16 08:46:38 +03:00
active_gradient: None,
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_gradient: None,
2025-02-16 08:46:38 +03:00
},
shadow: ShadowRule {
off: false,
on: false,
offset: None,
softness: None,
spread: None,
draw_behind_window: None,
color: None,
inactive_color: None,
},
tab_indicator: TabIndicatorRule {
active_color: Some(
Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
),
2025-02-16 08:46:38 +03:00
inactive_color: None,
2025-05-10 22:34:53 +03:00
urgent_color: None,
2025-02-16 08:46:38 +03:00
active_gradient: None,
inactive_gradient: None,
2025-05-10 22:34:53 +03:00
urgent_gradient: None,
},
2025-02-16 08:46:38 +03:00
draw_border_with_background: None,
opacity: None,
geometry_corner_radius: None,
clip_to_geometry: None,
baba_is_float: None,
block_out_from: None,
variable_refresh_rate: None,
default_column_display: Some(
Tabbed,
),
2025-02-16 08:46:38 +03:00
default_floating_position: Some(
FloatingPosition {
x: FloatOrInt(
100.0,
),
y: FloatOrInt(
-200.0,
),
2025-02-16 08:46:38 +03:00
relative_to: BottomLeft,
},
),
2025-02-16 08:46:38 +03:00
scroll_factor: None,
tiled_state: None,
2025-02-16 08:46:38 +03:00
},
],
layer_rules: [
LayerRule {
matches: [
Match {
namespace: Some(
RegexEq(
Regex(
"^notifications$",
),
),
),
2025-02-16 08:46:38 +03:00
at_startup: None,
2024-05-15 19:38:29 +04:00
},
2025-02-16 08:46:38 +03:00
],
excludes: [],
opacity: None,
block_out_from: Some(
Screencast,
),
shadow: ShadowRule {
off: false,
on: false,
offset: None,
softness: None,
spread: None,
draw_behind_window: None,
color: None,
inactive_color: None,
2024-05-15 19:38:29 +04:00
},
2025-02-16 08:46:38 +03:00
geometry_corner_radius: None,
2025-05-06 16:51:18 +03:00
place_within_backdrop: None,
2025-05-12 08:16:01 +03:00
baba_is_float: None,
2025-02-16 08:46:38 +03:00
},
],
binds: Binds(
[
Bind {
key: Key {
trigger: Keysym(
XK_Escape,
),
2025-02-16 08:46:38 +03:00
modifiers: Modifiers(
COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: ToggleKeyboardShortcutsInhibit,
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: false,
hotkey_overlay_title: Some(
Some(
"Inhibit",
),
2025-02-16 08:46:38 +03:00
),
},
Bind {
key: Key {
trigger: Keysym(
XK_Escape,
),
modifiers: Modifiers(
SHIFT | COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: ToggleKeyboardShortcutsInhibit,
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: false,
hotkey_overlay_title: None,
2024-02-24 10:08:56 +04:00
},
2025-02-16 08:46:38 +03:00
Bind {
key: Key {
trigger: Keysym(
XK_t,
),
2025-02-16 08:46:38 +03:00
modifiers: Modifiers(
COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: Spawn(
[
"alacritty",
],
),
repeat: true,
cooldown: None,
allow_when_locked: true,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
Bind {
key: Key {
trigger: Keysym(
XK_q,
),
2025-02-16 08:46:38 +03:00
modifiers: Modifiers(
COMPOSITOR,
),
2025-02-16 08:46:38 +03:00
},
action: CloseWindow,
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: Some(
None,
),
2025-02-16 08:46:38 +03:00
},
Bind {
key: Key {
trigger: Keysym(
XK_h,
),
2025-02-16 08:46:38 +03:00
modifiers: Modifiers(
SHIFT | COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: FocusMonitorLeft,
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
Bind {
key: Key {
trigger: Keysym(
XK_o,
),
modifiers: Modifiers(
SHIFT | COMPOSITOR,
),
},
action: FocusMonitor(
"eDP-1",
),
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
2025-02-16 08:46:38 +03:00
Bind {
key: Key {
trigger: Keysym(
XK_l,
),
modifiers: Modifiers(
CTRL | SHIFT | COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: MoveWindowToMonitorRight,
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
2025-02-02 08:41:42 +03:00
},
Bind {
key: Key {
trigger: Keysym(
XK_o,
),
modifiers: Modifiers(
CTRL | ALT | COMPOSITOR,
),
},
action: MoveWindowToMonitor(
"eDP-1",
),
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
Bind {
key: Key {
trigger: Keysym(
XK_p,
),
modifiers: Modifiers(
CTRL | ALT | COMPOSITOR,
),
},
action: MoveColumnToMonitor(
"DP-1",
),
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
2025-02-16 08:46:38 +03:00
Bind {
key: Key {
trigger: Keysym(
XK_comma,
),
modifiers: Modifiers(
COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: ConsumeWindowIntoColumn,
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
2024-05-11 22:40:30 +02:00
},
2025-02-16 08:46:38 +03:00
Bind {
key: Key {
trigger: Keysym(
XK_1,
),
2025-02-16 08:46:38 +03:00
modifiers: Modifiers(
COMPOSITOR,
),
2023-09-05 12:58:51 +04:00
},
2025-02-16 08:46:38 +03:00
action: FocusWorkspace(
Index(
1,
),
2025-02-16 08:46:38 +03:00
),
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
Bind {
key: Key {
trigger: Keysym(
XK_1,
),
2025-02-16 08:46:38 +03:00
modifiers: Modifiers(
SHIFT | COMPOSITOR,
),
2024-05-11 22:40:30 +02:00
},
2025-02-16 08:46:38 +03:00
action: FocusWorkspace(
Name(
"workspace-1",
),
),
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
Bind {
key: Key {
trigger: Keysym(
XK_e,
),
modifiers: Modifiers(
SHIFT | COMPOSITOR,
),
},
2025-02-16 08:46:38 +03:00
action: Quit(
true,
),
repeat: true,
cooldown: None,
allow_when_locked: false,
allow_inhibiting: false,
hotkey_overlay_title: None,
},
Bind {
key: Key {
trigger: WheelScrollDown,
modifiers: Modifiers(
COMPOSITOR,
),
2024-03-22 10:36:19 +04:00
},
2025-02-16 08:46:38 +03:00
action: FocusWorkspaceDown,
repeat: true,
cooldown: Some(
150ms,
),
allow_when_locked: false,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
2025-08-20 14:31:34 +03:00
Bind {
key: Key {
trigger: Keysym(
XK_s,
),
modifiers: Modifiers(
ALT | SUPER,
),
},
action: SpawnSh(
"pkill orca || exec orca",
),
repeat: true,
cooldown: None,
allow_when_locked: true,
allow_inhibiting: true,
hotkey_overlay_title: None,
},
2025-02-16 08:46:38 +03:00
],
),
switch_events: SwitchBinds {
lid_open: None,
lid_close: None,
tablet_mode_on: Some(
SwitchAction {
spawn: [
"bash",
"-c",
"gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true",
],
},
),
2025-02-16 08:46:38 +03:00
tablet_mode_off: Some(
SwitchAction {
spawn: [
"bash",
"-c",
"gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false",
],
},
),
},
debug: DebugConfig {
preview_render: None,
dbus_interfaces_in_non_session_instances: false,
wait_for_frame_completion_before_queueing: false,
enable_overlay_planes: false,
disable_cursor_plane: false,
disable_direct_scanout: false,
keep_max_bpc_unchanged: false,
2025-02-16 08:46:38 +03:00
restrict_primary_scanout_to_matching_format: false,
render_drm_device: Some(
"/dev/dri/renderD129",
),
force_pipewire_invalid_modifier: false,
emulate_zero_presentation_time: false,
disable_resize_throttling: false,
disable_transactions: false,
keep_laptop_panel_on_when_lid_is_closed: false,
disable_monitor_names: false,
strict_new_window_focus_policy: false,
honor_xdg_activation_with_invalid_serial: false,
deactivate_unfocused_windows: false,
skip_cursor_only_updates_during_vrr: false,
2025-02-16 08:46:38 +03:00
},
workspaces: [
Workspace {
name: WorkspaceName(
"workspace-1",
),
2025-02-16 08:46:38 +03:00
open_on_output: Some(
"eDP-1",
),
2024-10-18 16:00:40 +02:00
},
2025-02-16 08:46:38 +03:00
Workspace {
name: WorkspaceName(
"workspace-2",
),
2025-02-16 08:46:38 +03:00
open_on_output: None,
2023-09-06 15:49:46 +04:00
},
2025-02-16 08:46:38 +03:00
Workspace {
name: WorkspaceName(
"workspace-3",
),
open_on_output: None,
},
],
}
"#);
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());
}
2024-12-28 11:40:16 +03:00
#[test]
fn parse_position_change() {
assert_eq!(
"10".parse::<PositionChange>().unwrap(),
PositionChange::SetFixed(10.),
);
assert_eq!(
"+10".parse::<PositionChange>().unwrap(),
PositionChange::AdjustFixed(10.),
);
assert_eq!(
"-10".parse::<PositionChange>().unwrap(),
PositionChange::AdjustFixed(-10.),
);
assert!("10%".parse::<PositionChange>().is_err());
assert!("+10%".parse::<PositionChange>().is_err());
assert!("-10%".parse::<PositionChange>().is_err());
assert!("-".parse::<PositionChange>().is_err());
assert!("10% ".parse::<PositionChange>().is_err());
}
#[test]
fn parse_gradient_interpolation() {
assert_eq!(
"srgb".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Srgb,
..Default::default()
}
);
assert_eq!(
"srgb-linear".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::SrgbLinear,
..Default::default()
}
);
assert_eq!(
"oklab".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklab,
..Default::default()
}
);
assert_eq!(
"oklch".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
..Default::default()
}
);
assert_eq!(
"oklch shorter hue"
.parse::<GradientInterpolation>()
.unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Shorter,
}
);
assert_eq!(
"oklch longer hue".parse::<GradientInterpolation>().unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Longer,
}
);
assert_eq!(
"oklch decreasing hue"
.parse::<GradientInterpolation>()
.unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Decreasing,
}
);
assert_eq!(
"oklch increasing hue"
.parse::<GradientInterpolation>()
.unwrap(),
GradientInterpolation {
color_space: GradientColorSpace::Oklch,
hue_interpolation: HueInterpolation::Increasing,
}
);
assert!("".parse::<GradientInterpolation>().is_err());
assert!("srgb shorter hue".parse::<GradientInterpolation>().is_err());
assert!("oklch shorter".parse::<GradientInterpolation>().is_err());
assert!("oklch shorter h".parse::<GradientInterpolation>().is_err());
assert!("oklch a hue".parse::<GradientInterpolation>().is_err());
assert!("oklch shorter hue a"
.parse::<GradientInterpolation>()
.is_err());
}
#[test]
2024-07-31 11:00:35 -04:00
fn parse_iso_level_shifts() {
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
},
);
2024-07-31 11:00:35 -04:00
assert_eq!(
"ISO_Level5_Shift+A".parse::<Key>().unwrap(),
Key {
trigger: Trigger::Keysym(Keysym::a),
modifiers: Modifiers::ISO_LEVEL5_SHIFT
},
);
assert_eq!(
"Mod3+A".parse::<Key>().unwrap(),
Key {
trigger: Trigger::Keysym(Keysym::a),
modifiers: Modifiers::ISO_LEVEL5_SHIFT
},
);
}
2024-05-19 17:55:54 +04:00
#[test]
fn default_repeat_params() {
let config = Config::parse("config.kdl", "").unwrap();
assert_eq!(config.input.keyboard.repeat_delay, 600);
assert_eq!(config.input.keyboard.repeat_rate, 25);
}
fn make_output_name(
connector: &str,
make: Option<&str>,
model: Option<&str>,
serial: Option<&str>,
) -> OutputName {
OutputName {
connector: connector.to_string(),
make: make.map(|x| x.to_string()),
model: model.map(|x| x.to_string()),
serial: serial.map(|x| x.to_string()),
}
}
#[test]
fn test_output_name_match() {
fn check(
target: &str,
connector: &str,
make: Option<&str>,
model: Option<&str>,
serial: Option<&str>,
) -> bool {
let name = make_output_name(connector, make, model, serial);
name.matches(target)
}
assert!(check("dp-2", "DP-2", None, None, None));
assert!(!check("dp-1", "DP-2", None, None, None));
assert!(check("dp-2", "DP-2", Some("a"), Some("b"), Some("c")));
assert!(check(
"some company some monitor 1234",
"DP-2",
Some("Some Company"),
Some("Some Monitor"),
Some("1234")
));
assert!(!check(
"some other company some monitor 1234",
"DP-2",
Some("Some Company"),
Some("Some Monitor"),
Some("1234")
));
assert!(!check(
"make model serial ",
"DP-2",
Some("make"),
Some("model"),
Some("serial")
));
assert!(check(
"make serial",
"DP-2",
Some("make"),
Some(""),
Some("serial")
));
assert!(check(
"make model unknown",
"DP-2",
Some("Make"),
Some("Model"),
None
));
assert!(check(
"unknown unknown serial",
"DP-2",
None,
None,
Some("Serial")
));
assert!(!check("unknown unknown unknown", "DP-2", None, None, None));
}
#[test]
fn test_output_name_sorting() {
let mut names = vec![
make_output_name("DP-2", None, None, None),
make_output_name("DP-1", None, None, None),
make_output_name("DP-3", Some("B"), Some("A"), Some("A")),
make_output_name("DP-3", Some("A"), Some("B"), Some("A")),
make_output_name("DP-3", Some("A"), Some("A"), Some("B")),
make_output_name("DP-3", None, Some("A"), Some("A")),
make_output_name("DP-3", Some("A"), None, Some("A")),
make_output_name("DP-3", Some("A"), Some("A"), None),
make_output_name("DP-5", Some("A"), Some("A"), Some("A")),
make_output_name("DP-4", Some("A"), Some("A"), Some("A")),
];
names.sort_by(|a, b| a.compare(b));
let names = names
.into_iter()
.map(|name| {
format!(
"{} | {}",
name.format_make_model_serial_or_connector(),
name.connector,
)
})
.collect::<Vec<_>>();
assert_debug_snapshot!(
names,
@r#"
2025-01-09 10:21:53 +03:00
[
"Unknown A A | DP-3",
"A Unknown A | DP-3",
"A A Unknown | DP-3",
"A A A | DP-4",
"A A A | DP-5",
"A A B | DP-3",
"A B A | DP-3",
"B A A | DP-3",
"DP-1 | DP-1",
"DP-2 | DP-2",
]
"#
);
}
#[test]
fn test_border_rule_on_off_merging() {
fn is_on(config: &str, rules: &[&str]) -> String {
let mut resolved = BorderRule {
off: false,
on: false,
width: None,
active_color: None,
inactive_color: None,
2025-03-22 19:04:24 +01:00
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
2025-03-22 19:04:24 +01:00
urgent_gradient: None,
};
for rule in rules.iter().copied() {
let rule = BorderRule {
off: rule == "off" || rule == "off,on",
on: rule == "on" || rule == "off,on",
..Default::default()
};
resolved.merge_with(&rule);
}
let config = Border {
off: config == "off",
..Default::default()
};
if resolved.resolve_against(config).off {
"off"
} else {
"on"
}
.to_owned()
}
assert_snapshot!(is_on("off", &[]), @"off");
assert_snapshot!(is_on("off", &["off"]), @"off");
assert_snapshot!(is_on("off", &["on"]), @"on");
assert_snapshot!(is_on("off", &["off,on"]), @"on");
assert_snapshot!(is_on("on", &[]), @"on");
assert_snapshot!(is_on("on", &["off"]), @"off");
assert_snapshot!(is_on("on", &["on"]), @"on");
assert_snapshot!(is_on("on", &["off,on"]), @"on");
assert_snapshot!(is_on("off", &["off", "off"]), @"off");
assert_snapshot!(is_on("off", &["off", "on"]), @"on");
2024-12-27 15:40:48 +03:00
assert_snapshot!(is_on("off", &["on", "off"]), @"off");
assert_snapshot!(is_on("off", &["on", "on"]), @"on");
assert_snapshot!(is_on("on", &["off", "off"]), @"off");
assert_snapshot!(is_on("on", &["off", "on"]), @"on");
2024-12-27 15:40:48 +03:00
assert_snapshot!(is_on("on", &["on", "off"]), @"off");
assert_snapshot!(is_on("on", &["on", "on"]), @"on");
}
2023-09-05 12:58:51 +04:00
}