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

1178 lines
37 KiB
Rust
Raw Normal View History

2024-01-07 09:07:22 +04:00
#[macro_use]
extern crate tracing;
use std::path::{Path, PathBuf};
2023-09-05 12:58:51 +04:00
use std::str::FromStr;
use bitflags::bitflags;
2024-01-07 09:07:22 +04:00
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
2024-02-10 09:33:32 +04:00
use niri_ipc::{LayoutSwitchTarget, SizeChange};
2023-09-24 11:04:30 +04:00
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
2023-12-05 08:04:46 +04:00
use smithay::input::keyboard::{Keysym, XkbConfig};
2024-01-08 10:15:29 +04:00
use smithay::reexports::input;
2023-09-05 12:58:51 +04:00
#[derive(knuffel::Decode, Debug, PartialEq)]
pub struct Config {
#[knuffel(child, default)]
pub input: Input,
#[knuffel(children(name = "output"))]
pub outputs: Vec<Output>,
2023-09-21 19:58:03 +04:00
#[knuffel(children(name = "spawn-at-startup"))]
pub spawn_at_startup: Vec<SpawnAtStartup>,
2023-09-05 12:58:51 +04:00
#[knuffel(child, default)]
pub layout: Layout,
2023-09-26 13:09:33 +04:00
#[knuffel(child, default)]
2023-09-26 13:44:37 +04:00
pub prefer_no_csd: bool,
#[knuffel(child, default)]
2023-10-01 17:42:56 +04:00
pub cursor: Cursor,
2023-10-31 14:23:54 +04:00
#[knuffel(
child,
unwrap(argument),
default = Some(String::from(
"~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
)))
]
pub screenshot_path: Option<String>,
2023-10-01 17:42:56 +04:00
#[knuffel(child, default)]
pub hotkey_overlay: HotkeyOverlay,
#[knuffel(child, default)]
2024-02-07 17:05:15 +04:00
pub animations: Animations,
#[knuffel(child, default)]
2023-09-05 12:58:51 +04:00
pub binds: Binds,
2023-09-06 15:49:46 +04:00
#[knuffel(child, default)]
pub debug: DebugConfig,
2023-09-05 12:58:51 +04:00
}
// FIXME: Add other devices.
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Input {
#[knuffel(child, default)]
pub keyboard: Keyboard,
#[knuffel(child, default)]
pub touchpad: Touchpad,
2023-10-03 17:02:07 +04:00
#[knuffel(child, default)]
2024-01-08 11:53:34 +04:00
pub mouse: Mouse,
#[knuffel(child, default)]
2023-10-03 17:02:07 +04:00
pub tablet: Tablet,
2023-12-28 09:36:10 +04:00
#[knuffel(child)]
pub disable_power_key_handling: bool,
2023-09-05 12:58:51 +04:00
}
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
pub struct Keyboard {
#[knuffel(child, default)]
pub xkb: Xkb,
2023-09-16 20:01:52 +04:00
// The defaults were chosen to match wlroots and sway.
#[knuffel(child, unwrap(argument), default = 600)]
pub repeat_delay: u16,
#[knuffel(child, unwrap(argument), default = 25)]
pub repeat_rate: u8,
#[knuffel(child, unwrap(argument), default)]
pub track_layout: TrackLayout,
2023-09-05 12:58:51 +04:00
}
2023-12-08 07:58:03 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)]
2023-09-05 12:58:51 +04:00
pub struct Xkb {
#[knuffel(child, unwrap(argument), default)]
pub rules: String,
#[knuffel(child, unwrap(argument), default)]
pub model: String,
#[knuffel(child, unwrap(argument))]
pub layout: Option<String>,
#[knuffel(child, unwrap(argument), default)]
pub variant: String,
#[knuffel(child, unwrap(argument))]
pub options: Option<String>,
}
2023-12-05 08:04:46 +04:00
impl Xkb {
pub fn to_xkb_config(&self) -> XkbConfig {
XkbConfig {
rules: &self.rules,
model: &self.model,
layout: self.layout.as_deref().unwrap_or("us"),
variant: &self.variant,
options: self.options.clone(),
}
}
}
2024-01-08 17:17:19 +04:00
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum CenterFocusedColumn {
/// Focusing a column will not center the column.
#[default]
Never,
/// The focused column will always be centered.
Always,
/// Focusing a column will center it if it doesn't fit on the screen together with the
/// previously focused column.
OnOverflow,
}
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
pub enum TrackLayout {
/// The layout change is global.
#[default]
Global,
/// The layout change is window local.
Window,
}
2023-09-05 12:58:51 +04:00
// FIXME: Add the rest of the settings.
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Touchpad {
#[knuffel(child)]
pub tap: bool,
#[knuffel(child)]
2024-01-08 10:24:00 +04:00
pub dwt: bool,
#[knuffel(child)]
2024-02-03 08:20:15 +04:00
pub dwtp: bool,
#[knuffel(child)]
2023-09-05 12:58:51 +04:00
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: f64,
2024-01-08 10:15:29 +04:00
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
2024-01-08 10:32:04 +04:00
#[knuffel(child, unwrap(argument, str))]
pub tap_button_map: Option<TapButtonMap>,
2024-01-08 10:15:29 +04:00
}
2024-01-08 11:53:34 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Mouse {
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: f64,
#[knuffel(child, unwrap(argument, str))]
pub accel_profile: Option<AccelProfile>,
}
2024-01-08 10:15:29 +04:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccelProfile {
Adaptive,
Flat,
}
impl From<AccelProfile> for input::AccelProfile {
fn from(value: AccelProfile) -> Self {
match value {
AccelProfile::Adaptive => Self::Adaptive,
AccelProfile::Flat => Self::Flat,
}
}
2023-09-05 12:58:51 +04:00
}
2024-01-08 10:32:04 +04:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TapButtonMap {
LeftRightMiddle,
LeftMiddleRight,
}
impl From<TapButtonMap> for input::TapButtonMap {
fn from(value: TapButtonMap) -> Self {
match value {
TapButtonMap::LeftRightMiddle => Self::LeftRightMiddle,
TapButtonMap::LeftMiddleRight => Self::LeftMiddleRight,
}
}
}
2023-10-03 17:02:07 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Tablet {
#[knuffel(child, unwrap(argument))]
pub map_to_output: Option<String>,
}
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Output {
2023-12-18 10:27:41 +04:00
#[knuffel(child)]
pub off: bool,
#[knuffel(argument)]
pub name: String,
#[knuffel(child, unwrap(argument), default = 1.)]
pub scale: f64,
2024-01-28 14:25:40 +01:00
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
pub transform: Transform,
2023-09-30 11:33:02 +04:00
#[knuffel(child)]
pub position: Option<Position>,
2023-10-03 08:35:24 +04:00
#[knuffel(child, unwrap(argument, str))]
pub mode: Option<Mode>,
}
impl Default for Output {
fn default() -> Self {
Self {
2023-12-18 10:27:41 +04:00
off: false,
name: String::new(),
scale: 1.,
2024-01-28 14:25:40 +01:00
transform: Transform::Normal,
2023-09-30 11:33:02 +04:00
position: None,
2023-10-03 08:35:24 +04:00
mode: None,
}
}
}
2024-01-28 14:25:40 +01:00
/// Output transform, which goes counter-clockwise.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Transform {
Normal,
_90,
_180,
_270,
Flipped,
Flipped90,
Flipped180,
Flipped270,
}
impl FromStr for Transform {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"normal" => Ok(Self::Normal),
"90" => Ok(Self::_90),
"180" => Ok(Self::_180),
"270" => Ok(Self::_270),
"flipped" => Ok(Self::Flipped),
"flipped-90" => Ok(Self::Flipped90),
"flipped-180" => Ok(Self::Flipped180),
"flipped-270" => Ok(Self::Flipped270),
_ => Err(miette!(concat!(
r#"invalid transform, can be "90", "180", "270", "#,
r#""flipped", "flipped-90", "flipped-180" or "flipped-270""#
))),
}
}
}
impl From<Transform> for smithay::utils::Transform {
fn from(value: Transform) -> Self {
match value {
Transform::Normal => Self::Normal,
Transform::_90 => Self::_90,
Transform::_180 => Self::_180,
Transform::_270 => Self::_270,
Transform::Flipped => Self::Flipped,
Transform::Flipped90 => Self::Flipped90,
Transform::Flipped180 => Self::Flipped180,
Transform::Flipped270 => Self::Flipped270,
}
}
}
2024-01-16 08:43:28 +04:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
2023-09-30 11:33:02 +04:00
pub struct Position {
#[knuffel(property)]
pub x: i32,
#[knuffel(property)]
pub y: i32,
}
2024-01-16 08:43:28 +04:00
#[derive(Debug, Clone, Copy, PartialEq)]
2023-10-03 08:35:24 +04:00
pub struct Mode {
pub width: u16,
pub height: u16,
pub refresh: Option<f64>,
}
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
pub struct Layout {
#[knuffel(child, default)]
pub focus_ring: FocusRing,
#[knuffel(child, default = default_border())]
pub border: FocusRing,
#[knuffel(child, unwrap(children), default)]
pub preset_column_widths: Vec<PresetWidth>,
#[knuffel(child)]
pub default_column_width: Option<DefaultColumnWidth>,
2024-01-08 17:17:19 +04:00
#[knuffel(child, unwrap(argument), default)]
pub center_focused_column: CenterFocusedColumn,
#[knuffel(child, unwrap(argument), default = 16)]
pub gaps: u16,
#[knuffel(child, default)]
pub struts: Struts,
}
2023-09-21 19:58:03 +04:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct SpawnAtStartup {
#[knuffel(arguments)]
pub command: Vec<String>,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
2023-09-26 13:09:33 +04:00
pub struct FocusRing {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = 4)]
pub width: u16,
#[knuffel(child, default = Color::new(127, 200, 255, 255))]
2023-09-26 13:09:33 +04:00
pub active_color: Color,
#[knuffel(child, default = Color::new(80, 80, 80, 255))]
2023-09-26 13:09:33 +04:00
pub inactive_color: Color,
}
impl Default for FocusRing {
fn default() -> Self {
Self {
off: false,
width: 4,
active_color: Color::new(127, 200, 255, 255),
inactive_color: Color::new(80, 80, 80, 255),
2023-09-26 13:09:33 +04:00
}
}
}
pub const fn default_border() -> FocusRing {
FocusRing {
off: true,
width: 4,
active_color: Color::new(255, 200, 127, 255),
inactive_color: Color::new(80, 80, 80, 255),
}
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
2023-09-26 13:09:33 +04:00
pub struct Color {
#[knuffel(argument)]
pub r: u8,
2023-09-26 13:09:33 +04:00
#[knuffel(argument)]
pub g: u8,
2023-09-26 13:09:33 +04:00
#[knuffel(argument)]
pub b: u8,
2023-09-26 13:09:33 +04:00
#[knuffel(argument)]
pub a: u8,
2023-09-26 13:09:33 +04:00
}
impl Color {
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
2023-09-26 13:09:33 +04:00
Self { r, g, b, a }
}
}
impl From<Color> for [f32; 4] {
fn from(c: Color) -> Self {
let [r, g, b, a] = [c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.);
[r * a, g * a, b * a, a]
2023-09-26 13:09:33 +04:00
}
}
2023-10-01 17:42:56 +04:00
#[derive(knuffel::Decode, Debug, PartialEq)]
pub struct Cursor {
#[knuffel(child, unwrap(argument), default = String::from("default"))]
pub xcursor_theme: String,
#[knuffel(child, unwrap(argument), default = 24)]
pub xcursor_size: u8,
}
impl Default for Cursor {
fn default() -> Self {
Self {
xcursor_theme: String::from("default"),
xcursor_size: 24,
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub enum PresetWidth {
Proportion(#[knuffel(argument)] f64),
Fixed(#[knuffel(argument)] i32),
}
2023-11-02 21:07:29 +04:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec<PresetWidth>);
2023-12-21 08:37:30 +04:00
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Struts {
#[knuffel(child, unwrap(argument), default)]
pub left: u16,
#[knuffel(child, unwrap(argument), default)]
pub right: u16,
#[knuffel(child, unwrap(argument), default)]
pub top: u16,
#[knuffel(child, unwrap(argument), default)]
pub bottom: u16,
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct HotkeyOverlay {
#[knuffel(child)]
pub skip_at_startup: bool,
}
2024-02-07 17:05:15 +04:00
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Animations {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = 1.)]
pub slowdown: f64,
#[knuffel(child, default = Animation::default_workspace_switch())]
pub workspace_switch: Animation,
#[knuffel(child, default = Animation::default_horizontal_view_movement())]
pub horizontal_view_movement: Animation,
#[knuffel(child, default = Animation::default_window_open())]
pub window_open: Animation,
#[knuffel(child, default = Animation::default_config_notification_open_close())]
pub config_notification_open_close: Animation,
}
impl Default for Animations {
fn default() -> Self {
Self {
off: false,
slowdown: 1.,
workspace_switch: Animation::default_workspace_switch(),
horizontal_view_movement: Animation::default_horizontal_view_movement(),
window_open: Animation::default_window_open(),
config_notification_open_close: Animation::default_config_notification_open_close(),
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct Animation {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument))]
pub duration_ms: Option<u32>,
#[knuffel(child, unwrap(argument))]
pub curve: Option<AnimationCurve>,
}
impl Animation {
pub const fn unfilled() -> Self {
Self {
off: false,
duration_ms: None,
curve: None,
}
}
pub const fn default() -> Self {
Self {
off: false,
duration_ms: Some(250),
curve: Some(AnimationCurve::EaseOutCubic),
}
}
pub const fn default_workspace_switch() -> Self {
Self::default()
}
pub const fn default_horizontal_view_movement() -> Self {
Self::default()
}
pub const fn default_config_notification_open_close() -> Self {
Self::default()
}
pub const fn default_window_open() -> Self {
Self {
duration_ms: Some(150),
curve: Some(AnimationCurve::EaseOutExpo),
..Self::default()
}
}
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
pub enum AnimationCurve {
EaseOutCubic,
EaseOutExpo,
}
2023-10-03 11:38:42 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
2023-09-05 12:58:51 +04:00
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
2023-10-03 11:38:42 +04:00
#[derive(knuffel::Decode, Debug, PartialEq)]
2023-09-05 12:58:51 +04:00
pub struct Bind {
#[knuffel(node_name)]
pub key: Key,
#[knuffel(children)]
pub actions: Vec<Action>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Key {
pub keysym: Keysym,
pub modifiers: Modifiers,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Modifiers : u8 {
const CTRL = 1;
const SHIFT = 2;
const ALT = 4;
const SUPER = 8;
const COMPOSITOR = 16;
}
}
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(skip)]
ChangeVt(i32),
Suspend,
2023-10-09 18:37:43 +04:00
PowerOffMonitors,
2023-09-05 12:58:51 +04:00
ToggleDebugTint,
Spawn(#[knuffel(arguments)] Vec<String>),
2023-10-30 20:29:03 +04:00
#[knuffel(skip)]
ConfirmScreenshot,
#[knuffel(skip)]
CancelScreenshot,
Screenshot,
2023-10-26 16:47:59 +04:00
ScreenshotScreen,
2023-10-10 12:42:24 +04:00
ScreenshotWindow,
2023-09-05 12:58:51 +04:00
CloseWindow,
FullscreenWindow,
FocusColumnLeft,
FocusColumnRight,
2023-12-29 07:51:14 +04:00
FocusColumnFirst,
FocusColumnLast,
2023-09-05 12:58:51 +04:00
FocusWindowDown,
FocusWindowUp,
FocusWindowOrWorkspaceDown,
FocusWindowOrWorkspaceUp,
2023-09-05 12:58:51 +04:00
MoveColumnLeft,
MoveColumnRight,
2023-12-29 08:01:02 +04:00
MoveColumnToFirst,
MoveColumnToLast,
2023-09-05 12:58:51 +04:00
MoveWindowDown,
MoveWindowUp,
MoveWindowDownOrToWorkspaceDown,
MoveWindowUpOrToWorkspaceUp,
ConsumeOrExpelWindowLeft,
ConsumeOrExpelWindowRight,
2023-09-05 12:58:51 +04:00
ConsumeWindowIntoColumn,
ExpelWindowFromColumn,
CenterColumn,
2023-09-05 12:58:51 +04:00
FocusWorkspaceDown,
FocusWorkspaceUp,
2023-09-16 12:14:02 +04:00
FocusWorkspace(#[knuffel(argument)] u8),
2023-09-05 12:58:51 +04:00
MoveWindowToWorkspaceDown,
MoveWindowToWorkspaceUp,
2023-09-16 12:14:02 +04:00
MoveWindowToWorkspace(#[knuffel(argument)] u8),
2024-01-15 10:31:44 +04:00
MoveColumnToWorkspaceDown,
MoveColumnToWorkspaceUp,
MoveColumnToWorkspace(#[knuffel(argument)] u8),
2023-10-14 20:42:10 +04:00
MoveWorkspaceDown,
MoveWorkspaceUp,
2023-09-05 12:58:51 +04:00
FocusMonitorLeft,
FocusMonitorRight,
FocusMonitorDown,
FocusMonitorUp,
MoveWindowToMonitorLeft,
MoveWindowToMonitorRight,
MoveWindowToMonitorDown,
MoveWindowToMonitorUp,
2024-01-15 10:36:59 +04:00
MoveColumnToMonitorLeft,
MoveColumnToMonitorRight,
MoveColumnToMonitorDown,
MoveColumnToMonitorUp,
2023-11-08 11:17:06 +04:00
SetWindowHeight(#[knuffel(argument, str)] SizeChange),
2023-09-05 12:58:51 +04:00
SwitchPresetColumnWidth,
MaximizeColumn,
2023-10-03 11:38:42 +04:00
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
2024-02-10 09:33:32 +04:00
SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget),
2024-01-18 19:20:46 +04:00
ShowHotkeyOverlay,
MoveWorkspaceToMonitorLeft,
MoveWorkspaceToMonitorRight,
MoveWorkspaceToMonitorDown,
MoveWorkspaceToMonitorUp,
2023-10-03 11:38:42 +04:00
}
2024-02-10 09:33:32 +04:00
impl From<niri_ipc::Action> for Action {
fn from(value: niri_ipc::Action) -> Self {
match value {
niri_ipc::Action::Quit => Self::Quit,
niri_ipc::Action::PowerOffMonitors => Self::PowerOffMonitors,
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
niri_ipc::Action::Screenshot => Self::Screenshot,
niri_ipc::Action::ScreenshotScreen => Self::ScreenshotScreen,
niri_ipc::Action::ScreenshotWindow => Self::ScreenshotWindow,
niri_ipc::Action::CloseWindow => Self::CloseWindow,
niri_ipc::Action::FullscreenWindow => Self::FullscreenWindow,
niri_ipc::Action::FocusColumnLeft => Self::FocusColumnLeft,
niri_ipc::Action::FocusColumnRight => Self::FocusColumnRight,
niri_ipc::Action::FocusColumnFirst => Self::FocusColumnFirst,
niri_ipc::Action::FocusColumnLast => Self::FocusColumnLast,
niri_ipc::Action::FocusWindowDown => Self::FocusWindowDown,
niri_ipc::Action::FocusWindowUp => Self::FocusWindowUp,
niri_ipc::Action::FocusWindowOrWorkspaceDown => Self::FocusWindowOrWorkspaceDown,
niri_ipc::Action::FocusWindowOrWorkspaceUp => Self::FocusWindowOrWorkspaceUp,
niri_ipc::Action::MoveColumnLeft => Self::MoveColumnLeft,
niri_ipc::Action::MoveColumnRight => Self::MoveColumnRight,
niri_ipc::Action::MoveColumnToFirst => Self::MoveColumnToFirst,
niri_ipc::Action::MoveColumnToLast => Self::MoveColumnToLast,
niri_ipc::Action::MoveWindowDown => Self::MoveWindowDown,
niri_ipc::Action::MoveWindowUp => Self::MoveWindowUp,
niri_ipc::Action::MoveWindowDownOrToWorkspaceDown => {
Self::MoveWindowDownOrToWorkspaceDown
}
niri_ipc::Action::MoveWindowUpOrToWorkspaceUp => Self::MoveWindowUpOrToWorkspaceUp,
niri_ipc::Action::ConsumeOrExpelWindowLeft => Self::ConsumeOrExpelWindowLeft,
niri_ipc::Action::ConsumeOrExpelWindowRight => Self::ConsumeOrExpelWindowRight,
niri_ipc::Action::ConsumeWindowIntoColumn => Self::ConsumeWindowIntoColumn,
niri_ipc::Action::ExpelWindowFromColumn => Self::ExpelWindowFromColumn,
niri_ipc::Action::CenterColumn => Self::CenterColumn,
niri_ipc::Action::FocusWorkspaceDown => Self::FocusWorkspaceDown,
niri_ipc::Action::FocusWorkspaceUp => Self::FocusWorkspaceUp,
niri_ipc::Action::FocusWorkspace { index } => Self::FocusWorkspace(index),
niri_ipc::Action::MoveWindowToWorkspaceDown => Self::MoveWindowToWorkspaceDown,
niri_ipc::Action::MoveWindowToWorkspaceUp => Self::MoveWindowToWorkspaceUp,
niri_ipc::Action::MoveWindowToWorkspace { index } => Self::MoveWindowToWorkspace(index),
niri_ipc::Action::MoveColumnToWorkspaceDown => Self::MoveColumnToWorkspaceDown,
niri_ipc::Action::MoveColumnToWorkspaceUp => Self::MoveColumnToWorkspaceUp,
niri_ipc::Action::MoveColumnToWorkspace { index } => Self::MoveColumnToWorkspace(index),
niri_ipc::Action::MoveWorkspaceDown => Self::MoveWorkspaceDown,
niri_ipc::Action::MoveWorkspaceUp => Self::MoveWorkspaceUp,
niri_ipc::Action::FocusMonitorLeft => Self::FocusMonitorLeft,
niri_ipc::Action::FocusMonitorRight => Self::FocusMonitorRight,
niri_ipc::Action::FocusMonitorDown => Self::FocusMonitorDown,
niri_ipc::Action::FocusMonitorUp => Self::FocusMonitorUp,
niri_ipc::Action::MoveWindowToMonitorLeft => Self::MoveWindowToMonitorLeft,
niri_ipc::Action::MoveWindowToMonitorRight => Self::MoveWindowToMonitorRight,
niri_ipc::Action::MoveWindowToMonitorDown => Self::MoveWindowToMonitorDown,
niri_ipc::Action::MoveWindowToMonitorUp => Self::MoveWindowToMonitorUp,
niri_ipc::Action::MoveColumnToMonitorLeft => Self::MoveColumnToMonitorLeft,
niri_ipc::Action::MoveColumnToMonitorRight => Self::MoveColumnToMonitorRight,
niri_ipc::Action::MoveColumnToMonitorDown => Self::MoveColumnToMonitorDown,
niri_ipc::Action::MoveColumnToMonitorUp => Self::MoveColumnToMonitorUp,
niri_ipc::Action::SetWindowHeight { change } => Self::SetWindowHeight(change),
niri_ipc::Action::SwitchPresetColumnWidth => Self::SwitchPresetColumnWidth,
niri_ipc::Action::MaximizeColumn => Self::MaximizeColumn,
niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change),
niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout),
niri_ipc::Action::ShowHotkeyOverlay => Self::ShowHotkeyOverlay,
niri_ipc::Action::MoveWorkspaceToMonitorLeft => Self::MoveWorkspaceToMonitorLeft,
niri_ipc::Action::MoveWorkspaceToMonitorRight => Self::MoveWorkspaceToMonitorRight,
niri_ipc::Action::MoveWorkspaceToMonitorDown => Self::MoveWorkspaceToMonitorDown,
niri_ipc::Action::MoveWorkspaceToMonitorUp => Self::MoveWorkspaceToMonitorUp,
niri_ipc::Action::ToggleDebugTint => Self::ToggleDebugTint,
}
}
2023-11-02 00:09:31 +04:00
}
2024-02-07 17:05:15 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
2023-09-06 15:49:46 +04:00
pub struct DebugConfig {
2023-09-08 17:54:02 +04:00
#[knuffel(child)]
pub dbus_interfaces_in_non_session_instances: bool,
2023-09-14 09:33:42 +04:00
#[knuffel(child)]
pub wait_for_frame_completion_before_queueing: bool,
#[knuffel(child)]
pub enable_color_transformations_capability: bool,
2023-09-14 22:43:12 +04:00
#[knuffel(child)]
pub enable_overlay_planes: bool,
2024-01-03 08:42:04 +04:00
#[knuffel(child)]
pub disable_cursor_plane: bool,
2024-01-06 09:14:48 +04:00
#[knuffel(child, unwrap(argument))]
pub render_drm_device: Option<PathBuf>,
2023-09-06 15:49:46 +04:00
}
2023-09-05 12:58:51 +04:00
impl Config {
pub fn load(path: &Path) -> miette::Result<Self> {
2024-01-16 12:53:01 +04:00
let _span = tracy_client::span!("Config::load");
2024-01-07 09:07:22 +04:00
Self::load_internal(path).context("error loading config")
}
fn load_internal(path: &Path) -> miette::Result<Self> {
let contents = std::fs::read_to_string(path)
2023-09-05 12:58:51 +04:00
.into_diagnostic()
.with_context(|| format!("error reading {path:?}"))?;
let config = Self::parse("config.kdl", &contents).context("error parsing")?;
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()
}
}
2023-10-03 08:35:24 +04:00
impl FromStr for Mode {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((width, rest)) = s.split_once('x') else {
return Err(miette!("no 'x' separator found"));
};
let (height, refresh) = match rest.split_once('@') {
Some((height, refresh)) => (height, Some(refresh)),
None => (rest, None),
};
let width = width
.parse()
.into_diagnostic()
.context("error parsing width")?;
let height = height
.parse()
.into_diagnostic()
.context("error parsing height")?;
let refresh = refresh
.map(str::parse)
.transpose()
.into_diagnostic()
.context("error parsing refresh rate")?;
Ok(Self {
width,
height,
refresh,
})
}
}
2023-09-05 12:58:51 +04:00
impl FromStr for Key {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut modifiers = Modifiers::empty();
let mut split = s.split('+');
let key = split.next_back().unwrap();
for part in split {
let part = part.trim();
if part.eq_ignore_ascii_case("mod") {
modifiers |= Modifiers::COMPOSITOR
} else if part.eq_ignore_ascii_case("ctrl") || part.eq_ignore_ascii_case("control") {
modifiers |= Modifiers::CTRL;
} else if part.eq_ignore_ascii_case("shift") {
modifiers |= Modifiers::SHIFT;
} else if part.eq_ignore_ascii_case("alt") {
modifiers |= Modifiers::ALT;
} else if part.eq_ignore_ascii_case("super") || part.eq_ignore_ascii_case("win") {
modifiers |= Modifiers::SUPER;
} else {
return Err(miette!("invalid modifier: {part}"));
}
}
let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
2023-09-24 11:04:30 +04:00
if keysym.raw() == KEY_NoSymbol {
2023-09-05 12:58:51 +04:00
return Err(miette!("invalid key: {key}"));
}
Ok(Key { keysym, modifiers })
}
}
2024-01-08 10:15:29 +04:00
impl FromStr for AccelProfile {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"adaptive" => Ok(Self::Adaptive),
"flat" => Ok(Self::Flat),
_ => Err(miette!(
r#"invalid accel profile, can be "adaptive" or "flat""#
)),
}
}
}
2024-01-08 10:32:04 +04:00
impl FromStr for TapButtonMap {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"left-right-middle" => Ok(Self::LeftRightMiddle),
"left-middle-right" => Ok(Self::LeftMiddleRight),
_ => Err(miette!(
r#"invalid tap button map, can be "left-right-middle" or "left-middle-right""#
)),
}
}
}
2024-01-07 09:07:22 +04:00
pub fn set_miette_hook() -> Result<(), miette::InstallError> {
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new())))
}
2023-09-05 12:58:51 +04:00
#[cfg(test)]
mod tests {
2023-10-03 07:41:05 +04:00
use miette::NarratableReportHandler;
2023-09-05 12:58:51 +04:00
use super::*;
#[track_caller]
fn check(text: &str, expected: Config) {
2023-10-03 07:41:05 +04:00
let _ = miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new())));
2023-09-05 12:58:51 +04:00
let parsed = Config::parse("test.kdl", text)
.map_err(miette::Report::new)
.unwrap();
assert_eq!(parsed, expected);
}
#[test]
fn parse() {
check(
r#"
input {
keyboard {
2023-09-16 20:01:52 +04:00
repeat-delay 600
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
2023-09-05 12:58:51 +04:00
accel-speed 0.2
2024-01-08 10:15:29 +04:00
accel-profile "flat"
2024-01-08 10:32:04 +04:00
tap-button-map "left-middle-right"
2023-09-05 12:58:51 +04:00
}
2023-10-03 17:02:07 +04:00
2024-01-08 11:53:34 +04:00
mouse {
natural-scroll
accel-speed 0.4
accel-profile "flat"
}
2023-10-03 17:02:07 +04:00
tablet {
map-to-output "eDP-1"
}
2023-12-28 09:36:10 +04:00
disable-power-key-handling
2023-09-05 12:58:51 +04:00
}
output "eDP-1" {
scale 2.0
2024-01-28 14:25:40 +01:00
transform "flipped-90"
2023-09-30 11:33:02 +04:00
position x=10 y=20
2023-10-03 08:35:24 +04:00
mode "1920x1080@144"
}
layout {
focus-ring {
width 5
active-color 0 100 200 255
inactive-color 255 200 100 0
}
2023-09-21 19:58:03 +04:00
border {
width 3
active-color 0 100 200 255
inactive-color 255 200 100 0
}
2023-09-26 13:09:33 +04:00
preset-column-widths {
proportion 0.25
proportion 0.5
fixed 960
fixed 1280
}
default-column-width { proportion 0.25; }
2023-09-26 13:44:37 +04:00
gaps 8
2023-10-01 17:42:56 +04:00
struts {
left 1
right 2
top 3
}
2024-01-08 17:17:19 +04:00
center-focused-column "on-overflow"
}
spawn-at-startup "alacritty" "-e" "fish"
2023-11-02 21:07:29 +04:00
prefer-no-csd
2023-10-07 17:45:55 +04:00
cursor {
xcursor-theme "breeze_cursors"
xcursor-size 16
2023-12-21 08:37:30 +04:00
}
2023-10-31 14:23:54 +04:00
screenshot-path "~/Screenshots/screenshot.png"
2023-10-31 08:57:44 +04:00
hotkey-overlay {
skip-at-startup
}
2024-02-07 17:05:15 +04:00
animations {
slowdown 2.0
workspace-switch { off; }
horizontal-view-movement {
duration-ms 100
curve "ease-out-expo"
}
}
2023-09-05 12:58:51 +04:00
binds {
Mod+T { spawn "alacritty"; }
Mod+Q { close-window; }
Mod+Shift+H { focus-monitor-left; }
Mod+Ctrl+Shift+L { move-window-to-monitor-right; }
Mod+Comma { consume-window-into-column; }
2024-02-12 07:48:31 +04:00
Mod+1 { focus-workspace 1; }
2023-09-05 12:58:51 +04:00
}
2023-09-06 15:49:46 +04:00
debug {
2024-01-06 09:14:48 +04:00
render-drm-device "/dev/dri/renderD129"
2023-09-06 15:49:46 +04:00
}
2023-09-05 12:58:51 +04:00
"#,
Config {
input: Input {
keyboard: Keyboard {
xkb: Xkb {
layout: Some("us,ru".to_owned()),
options: Some("grp:win_space_toggle".to_owned()),
..Default::default()
},
2023-09-16 20:01:52 +04:00
repeat_delay: 600,
repeat_rate: 25,
track_layout: TrackLayout::Window,
2023-09-05 12:58:51 +04:00
},
touchpad: Touchpad {
tap: true,
2024-01-08 10:24:00 +04:00
dwt: true,
2024-02-03 08:20:15 +04:00
dwtp: true,
2023-09-05 12:58:51 +04:00
natural_scroll: false,
accel_speed: 0.2,
2024-01-08 10:15:29 +04:00
accel_profile: Some(AccelProfile::Flat),
2024-01-08 10:32:04 +04:00
tap_button_map: Some(TapButtonMap::LeftMiddleRight),
2023-09-05 12:58:51 +04:00
},
2024-01-08 11:53:34 +04:00
mouse: Mouse {
natural_scroll: true,
accel_speed: 0.4,
accel_profile: Some(AccelProfile::Flat),
},
2023-10-03 17:02:07 +04:00
tablet: Tablet {
map_to_output: Some("eDP-1".to_owned()),
},
2023-12-28 09:36:10 +04:00
disable_power_key_handling: true,
2023-09-05 12:58:51 +04:00
},
outputs: vec![Output {
2023-12-18 10:27:41 +04:00
off: false,
name: "eDP-1".to_owned(),
scale: 2.,
2024-01-28 14:25:40 +01:00
transform: Transform::Flipped90,
2023-09-30 11:33:02 +04:00
position: Some(Position { x: 10, y: 20 }),
2023-10-03 08:35:24 +04:00
mode: Some(Mode {
width: 1920,
height: 1080,
refresh: Some(144.),
}),
}],
layout: Layout {
focus_ring: FocusRing {
off: false,
width: 5,
active_color: Color {
r: 0,
g: 100,
b: 200,
a: 255,
},
inactive_color: Color {
r: 255,
g: 200,
b: 100,
a: 0,
},
2023-09-26 13:09:33 +04:00
},
border: FocusRing {
off: false,
width: 3,
active_color: Color {
r: 0,
g: 100,
b: 200,
a: 255,
},
inactive_color: Color {
r: 255,
g: 200,
b: 100,
a: 0,
},
},
preset_column_widths: vec![
PresetWidth::Proportion(0.25),
PresetWidth::Proportion(0.5),
PresetWidth::Fixed(960),
PresetWidth::Fixed(1280),
],
default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion(
0.25,
)])),
gaps: 8,
struts: Struts {
left: 1,
right: 2,
top: 3,
bottom: 0,
},
2024-01-08 17:17:19 +04:00
center_focused_column: CenterFocusedColumn::OnOverflow,
},
spawn_at_startup: vec![SpawnAtStartup {
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
}],
2023-09-26 13:44:37 +04:00
prefer_no_csd: true,
2023-10-01 17:42:56 +04:00
cursor: Cursor {
xcursor_theme: String::from("breeze_cursors"),
xcursor_size: 16,
},
2023-10-31 14:23:54 +04:00
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
hotkey_overlay: HotkeyOverlay {
skip_at_startup: true,
},
2024-02-07 17:05:15 +04:00
animations: Animations {
slowdown: 2.,
workspace_switch: Animation {
off: true,
..Animation::unfilled()
},
horizontal_view_movement: Animation {
duration_ms: Some(100),
curve: Some(AnimationCurve::EaseOutExpo),
..Animation::unfilled()
},
..Default::default()
},
2023-09-05 12:58:51 +04:00
binds: Binds(vec![
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::t,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::Spawn(vec!["alacritty".to_owned()])],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::q,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::CloseWindow],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::h,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
actions: vec![Action::FocusMonitorLeft],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::l,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
},
actions: vec![Action::MoveWindowToMonitorRight],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::comma,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::ConsumeWindowIntoColumn],
},
2023-09-16 12:14:02 +04:00
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::_1,
2023-09-16 12:14:02 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::FocusWorkspace(1)],
},
2023-09-05 12:58:51 +04:00
]),
2023-09-06 15:49:46 +04:00
debug: DebugConfig {
2024-01-06 09:14:48 +04:00
render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")),
2023-09-08 17:54:02 +04:00
..Default::default()
2023-09-06 15:49:46 +04:00
},
2023-09-05 12:58:51 +04:00
},
);
}
#[test]
fn can_create_default_config() {
let _ = Config::default();
}
2023-10-03 08:35:24 +04:00
#[test]
fn parse_mode() {
assert_eq!(
"2560x1600@165.004".parse::<Mode>().unwrap(),
Mode {
width: 2560,
height: 1600,
refresh: Some(165.004),
},
);
assert_eq!(
"1920x1080".parse::<Mode>().unwrap(),
Mode {
width: 1920,
height: 1080,
refresh: None,
},
);
assert!("1920".parse::<Mode>().is_err());
assert!("1920x".parse::<Mode>().is_err());
assert!("1920x1080@".parse::<Mode>().is_err());
assert!("1920x1080@60Hz".parse::<Mode>().is_err());
}
2023-10-03 11:38:42 +04:00
#[test]
fn parse_size_change() {
assert_eq!(
"10".parse::<SizeChange>().unwrap(),
SizeChange::SetFixed(10),
);
assert_eq!(
"+10".parse::<SizeChange>().unwrap(),
SizeChange::AdjustFixed(10),
);
assert_eq!(
"-10".parse::<SizeChange>().unwrap(),
SizeChange::AdjustFixed(-10),
);
assert_eq!(
"10%".parse::<SizeChange>().unwrap(),
SizeChange::SetProportion(10.),
);
assert_eq!(
"+10%".parse::<SizeChange>().unwrap(),
SizeChange::AdjustProportion(10.),
);
assert_eq!(
"-10%".parse::<SizeChange>().unwrap(),
SizeChange::AdjustProportion(-10.),
);
assert!("-".parse::<SizeChange>().is_err());
assert!("10% ".parse::<SizeChange>().is_err());
}
2023-09-05 12:58:51 +04:00
}