aboutsummaryrefslogtreecommitdiff
path: root/src/config.rs
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2023-09-05 12:58:51 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2023-09-05 12:58:59 +0400
commit5225bc9e558cd87ed54271e47dcddaac2d5bcf62 (patch)
tree554fa51a59abc2abad1aefac6d1cf2d5e246b5ec /src/config.rs
parentbdc86032e44a5c84b4552cd1ad2bbbca07955e23 (diff)
downloadniri-5225bc9e558cd87ed54271e47dcddaac2d5bcf62.tar.gz
niri-5225bc9e558cd87ed54271e47dcddaac2d5bcf62.tar.bz2
niri-5225bc9e558cd87ed54271e47dcddaac2d5bcf62.zip
Add configuration file
Diffstat (limited to 'src/config.rs')
-rw-r--r--src/config.rs297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 00000000..a7d6fd7d
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,297 @@
+use std::path::PathBuf;
+use std::str::FromStr;
+
+use bitflags::bitflags;
+use directories::ProjectDirs;
+use miette::{miette, Context, IntoDiagnostic};
+use smithay::input::keyboard::xkb::{keysym_from_name, KEY_NoSymbol, KEYSYM_CASE_INSENSITIVE};
+use smithay::input::keyboard::Keysym;
+
+#[derive(knuffel::Decode, Debug, PartialEq)]
+pub struct Config {
+ #[knuffel(child, default)]
+ pub input: Input,
+ #[knuffel(child, default)]
+ pub binds: Binds,
+}
+
+// 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,
+}
+
+#[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, 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,
+ MoveWindowToWorkspaceDown,
+ MoveWindowToWorkspaceUp,
+ FocusMonitorLeft,
+ FocusMonitorRight,
+ FocusMonitorDown,
+ FocusMonitorUp,
+ MoveWindowToMonitorLeft,
+ MoveWindowToMonitorRight,
+ MoveWindowToMonitorDown,
+ MoveWindowToMonitorUp,
+ SwitchPresetColumnWidth,
+ MaximizeColumn,
+}
+
+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);
+ if keysym == KEY_NoSymbol {
+ return Err(miette!("invalid key: {key}"));
+ }
+
+ Ok(Key { keysym, modifiers })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use smithay::input::keyboard::xkb::keysyms::*;
+
+ 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 {
+ xkb {
+ layout "us,ru"
+ options "grp:win_space_toggle"
+ }
+ }
+
+ touchpad {
+ tap
+ accel-speed 0.2
+ }
+ }
+
+ 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; }
+ }
+ "#,
+ Config {
+ input: Input {
+ keyboard: Keyboard {
+ xkb: Xkb {
+ layout: Some("us,ru".to_owned()),
+ options: Some("grp:win_space_toggle".to_owned()),
+ ..Default::default()
+ },
+ },
+ touchpad: Touchpad {
+ tap: true,
+ natural_scroll: false,
+ accel_speed: 0.2,
+ },
+ },
+ binds: Binds(vec![
+ Bind {
+ key: Key {
+ keysym: KEY_t,
+ modifiers: Modifiers::COMPOSITOR,
+ },
+ actions: vec![Action::Spawn(vec!["alacritty".to_owned()])],
+ },
+ Bind {
+ key: Key {
+ keysym: KEY_q,
+ modifiers: Modifiers::COMPOSITOR,
+ },
+ actions: vec![Action::CloseWindow],
+ },
+ Bind {
+ key: Key {
+ keysym: KEY_h,
+ modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
+ },
+ actions: vec![Action::FocusMonitorLeft],
+ },
+ Bind {
+ key: Key {
+ keysym: KEY_l,
+ modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
+ },
+ actions: vec![Action::MoveWindowToMonitorRight],
+ },
+ Bind {
+ key: Key {
+ keysym: KEY_comma,
+ modifiers: Modifiers::COMPOSITOR,
+ },
+ actions: vec![Action::ConsumeWindowIntoColumn],
+ },
+ ]),
+ },
+ );
+ }
+
+ #[test]
+ fn can_create_default_config() {
+ let _ = Config::default();
+ }
+}