Files
niri/src/config.rs
T

392 lines
11 KiB
Rust
Raw Normal View History

2023-09-05 12:58:51 +04:00
use std::path::PathBuf;
use std::str::FromStr;
use bitflags::bitflags;
use directories::ProjectDirs;
use miette::{miette, Context, IntoDiagnostic};
2023-09-24 11:04:30 +04:00
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
2023-09-05 12:58:51 +04:00
use smithay::input::keyboard::Keysym;
#[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 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,
}
#[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,
2023-09-05 12:58:51 +04:00
}
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
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>,
}
// FIXME: Add the rest of the settings.
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Touchpad {
#[knuffel(child)]
pub tap: bool,
#[knuffel(child)]
pub natural_scroll: bool,
#[knuffel(child, unwrap(argument), default)]
pub accel_speed: f64,
}
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Output {
#[knuffel(argument)]
pub name: String,
#[knuffel(child, unwrap(argument), default = 1.)]
pub scale: f64,
}
impl Default for Output {
fn default() -> Self {
Self {
name: String::new(),
scale: 1.,
}
}
}
2023-09-21 19:58:03 +04:00
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct SpawnAtStartup {
#[knuffel(arguments)]
pub command: Vec<String>,
}
2023-09-05 12:58:51 +04:00
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
#[derive(knuffel::Decode, Debug, PartialEq, Eq)]
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;
}
}
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub enum Action {
#[knuffel(skip)]
None,
Quit,
#[knuffel(skip)]
ChangeVt(i32),
Suspend,
ToggleDebugTint,
Spawn(#[knuffel(arguments)] Vec<String>),
Screenshot,
CloseWindow,
FullscreenWindow,
FocusColumnLeft,
FocusColumnRight,
FocusWindowDown,
FocusWindowUp,
MoveColumnLeft,
MoveColumnRight,
MoveWindowDown,
MoveWindowUp,
ConsumeWindowIntoColumn,
ExpelWindowFromColumn,
FocusWorkspaceDown,
FocusWorkspaceUp,
2023-09-16 12:14:02 +04:00
FocusWorkspace(#[knuffel(argument)] u8),
2023-09-05 12:58:51 +04:00
MoveWindowToWorkspaceDown,
MoveWindowToWorkspaceUp,
2023-09-16 12:14:02 +04:00
MoveWindowToWorkspace(#[knuffel(argument)] u8),
2023-09-05 12:58:51 +04:00
FocusMonitorLeft,
FocusMonitorRight,
FocusMonitorDown,
FocusMonitorUp,
MoveWindowToMonitorLeft,
MoveWindowToMonitorRight,
MoveWindowToMonitorDown,
MoveWindowToMonitorUp,
SwitchPresetColumnWidth,
MaximizeColumn,
}
2023-09-06 15:49:46 +04:00
#[derive(knuffel::Decode, Debug, PartialEq)]
pub struct DebugConfig {
#[knuffel(child, unwrap(argument), default = 1.)]
pub animation_slowdown: f64,
2023-09-08 17:54:02 +04:00
#[knuffel(child)]
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,
2023-09-06 15:49:46 +04:00
}
impl Default for DebugConfig {
fn default() -> Self {
Self {
animation_slowdown: 1.,
dbus_interfaces_in_non_session_instances: false,
2023-09-14 09:33:42 +04:00
wait_for_frame_completion_before_queueing: false,
enable_color_transformations_capability: false,
2023-09-14 22:43:12 +04:00
enable_overlay_planes: false,
2023-09-06 15:49:46 +04:00
}
}
}
2023-09-05 12:58:51 +04:00
impl Config {
pub fn load(path: Option<PathBuf>) -> miette::Result<Self> {
let path = if let Some(path) = path {
path
} else {
let mut path = ProjectDirs::from("", "", "niri")
.ok_or_else(|| miette!("error retrieving home directory"))?
.config_dir()
.to_owned();
path.push("config.kdl");
path
};
let contents = std::fs::read_to_string(&path)
.into_diagnostic()
.with_context(|| format!("error reading {path:?}"))?;
let config = Self::parse("config.kdl", &contents).context("error parsing")?;
debug!("loaded config from {path:?}");
Ok(config)
}
pub fn parse(filename: &str, text: &str) -> Result<Self, knuffel::Error> {
knuffel::parse(filename, text)
}
}
impl Default for Config {
fn default() -> Self {
Config::parse(
"default-config.kdl",
include_str!("../resources/default-config.kdl"),
)
.unwrap()
}
}
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 })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn check(text: &str, expected: Config) {
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
2023-09-05 12:58:51 +04:00
xkb {
layout "us,ru"
options "grp:win_space_toggle"
}
}
touchpad {
tap
accel-speed 0.2
}
}
output "eDP-1" {
scale 2.0
}
2023-09-21 19:58:03 +04:00
spawn-at-startup "alacritty" "-e" "fish"
2023-09-05 12:58:51 +04:00
binds {
Mod+T { spawn "alacritty"; }
Mod+Q { close-window; }
Mod+Shift+H { focus-monitor-left; }
Mod+Ctrl+Shift+L { move-window-to-monitor-right; }
Mod+Comma { consume-window-into-column; }
2023-09-16 12:14:02 +04:00
Mod+1 { focus-workspace 1;}
2023-09-05 12:58:51 +04:00
}
2023-09-06 15:49:46 +04:00
debug {
animation-slowdown 2.0
}
2023-09-05 12:58:51 +04:00
"#,
Config {
input: Input {
keyboard: Keyboard {
xkb: Xkb {
layout: Some("us,ru".to_owned()),
options: Some("grp:win_space_toggle".to_owned()),
..Default::default()
},
2023-09-16 20:01:52 +04:00
repeat_delay: 600,
repeat_rate: 25,
2023-09-05 12:58:51 +04:00
},
touchpad: Touchpad {
tap: true,
natural_scroll: false,
accel_speed: 0.2,
},
},
outputs: vec![Output {
name: "eDP-1".to_owned(),
scale: 2.,
}],
2023-09-21 19:58:03 +04:00
spawn_at_startup: vec![SpawnAtStartup {
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
}],
2023-09-05 12:58:51 +04:00
binds: Binds(vec![
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::t,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::Spawn(vec!["alacritty".to_owned()])],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::q,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::CloseWindow],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::h,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
actions: vec![Action::FocusMonitorLeft],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::l,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
},
actions: vec![Action::MoveWindowToMonitorRight],
},
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::comma,
2023-09-05 12:58:51 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::ConsumeWindowIntoColumn],
},
2023-09-16 12:14:02 +04:00
Bind {
key: Key {
2023-09-24 11:04:30 +04:00
keysym: Keysym::_1,
2023-09-16 12:14:02 +04:00
modifiers: Modifiers::COMPOSITOR,
},
actions: vec![Action::FocusWorkspace(1)],
},
2023-09-05 12:58:51 +04:00
]),
2023-09-06 15:49:46 +04:00
debug: DebugConfig {
animation_slowdown: 2.,
2023-09-08 17:54:02 +04:00
..Default::default()
2023-09-06 15:49:46 +04:00
},
2023-09-05 12:58:51 +04:00
},
);
}
#[test]
fn can_create_default_config() {
let _ = Config::default();
}
}