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,
|
2023-09-21 13:48:32 +04:00
|
|
|
#[knuffel(children(name = "output"))]
|
|
|
|
|
pub outputs: Vec<Output>,
|
2023-09-21 19:58:03 +04:00
|
|
|
#[knuffel(children(name = "spawn-at-startup"))]
|
|
|
|
|
pub spawn_at_startup: Vec<SpawnAtStartup>,
|
2023-09-05 12:58:51 +04:00
|
|
|
#[knuffel(child, default)]
|
|
|
|
|
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)]
|
2023-09-16 12:37:23 +04:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
#[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)]
|
2023-09-20 09:28:23 +04:00
|
|
|
pub dbus_interfaces_in_non_session_instances: bool,
|
2023-09-14 09:33:42 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub wait_for_frame_completion_before_queueing: bool,
|
2023-09-14 22:33:49 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub enable_color_transformations_capability: bool,
|
2023-09-14 22:43:12 +04:00
|
|
|
#[knuffel(child)]
|
|
|
|
|
pub enable_overlay_planes: bool,
|
2023-09-06 15:49:46 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for DebugConfig {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
animation_slowdown: 1.,
|
2023-09-20 09:28:23 +04:00
|
|
|
dbus_interfaces_in_non_session_instances: false,
|
2023-09-14 09:33:42 +04:00
|
|
|
wait_for_frame_completion_before_queueing: false,
|
2023-09-14 22:33:49 +04:00
|
|
|
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
|
2023-09-16 12:37:23 +04:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
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,
|
2023-09-16 12:37:23 +04:00
|
|
|
repeat_rate: 25,
|
2023-09-05 12:58:51 +04:00
|
|
|
},
|
|
|
|
|
touchpad: Touchpad {
|
|
|
|
|
tap: true,
|
|
|
|
|
natural_scroll: false,
|
|
|
|
|
accel_speed: 0.2,
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-09-21 13:48:32 +04:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|