Add configuration file

This commit is contained in:
Ivan Molodetskikh 2023-09-05 12:58:51 +04:00
parent bdc86032e4
commit 5225bc9e55
8 changed files with 781 additions and 128 deletions

268
Cargo.lock generated
View File

@ -2,12 +2,32 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "1.0.5"
@ -244,6 +264,36 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "backtrace-ext"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
dependencies = [
"backtrace",
]
[[package]]
name = "base64"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -377,6 +427,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "chumsky"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d"
dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "clap"
version = "4.4.2"
@ -844,6 +903,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "gimli"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "gl_generator"
version = "0.14.0"
@ -860,6 +925,9 @@ name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
@ -872,6 +940,9 @@ name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
@ -964,6 +1035,23 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.11",
"windows-sys 0.48.0",
]
[[package]]
name = "is_ci"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
name = "itoa"
version = "1.0.9"
@ -1009,6 +1097,33 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "knuffel"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04bee6ddc6071011314b1ce4f7705fef6c009401dba4fd22cb0009db6a177413"
dependencies = [
"base64",
"chumsky",
"knuffel-derive",
"miette",
"thiserror",
"unicode-width",
]
[[package]]
name = "knuffel-derive"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91977f56c49cfb961e3d840e2e7c6e4a56bde7283898cf606861f1421348283d"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1159,6 +1274,38 @@ dependencies = [
"autocfg",
]
[[package]]
name = "miette"
version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
dependencies = [
"backtrace",
"backtrace-ext",
"is-terminal",
"miette-derive",
"once_cell",
"owo-colors",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"terminal_size",
"textwrap",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.31",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1226,9 +1373,12 @@ dependencies = [
"directories",
"image",
"keyframe",
"knuffel",
"logind-zbus",
"miette",
"profiling",
"sd-notify",
"serde",
"smithay",
"smithay-drm-extras",
"time",
@ -1405,6 +1555,15 @@ dependencies = [
"objc-sys",
]
[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
@ -1442,6 +1601,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "parking"
version = "2.1.0"
@ -1517,6 +1682,30 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.66"
@ -1673,6 +1862,12 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.37.23"
@ -1824,6 +2019,12 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "smithay"
version = "0.3.0"
@ -1917,6 +2118,34 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "supports-color"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
dependencies = [
"is-terminal",
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
dependencies = [
"is-terminal",
]
[[package]]
name = "supports-unicode"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
dependencies = [
"is-terminal",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -1952,6 +2181,27 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.48"
@ -2144,6 +2394,24 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-linebreak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "utf8parse"
version = "0.2.1"

View File

@ -13,9 +13,12 @@ clap = { version = "4.3.21", features = ["derive"] }
directories = "5.0.1"
image = { version = "0.24.7", default-features = false, features = ["png"] }
keyframe = { version = "1.1.1", default-features = false }
knuffel = "3.2.0"
logind-zbus = "3.1.2"
miette = { version = "5.10.0", features = ["fancy"] }
profiling = "1.0.9"
sd-notify = "0.4.1"
serde = { version = "1.0.188", features = ["derive"] }
time = { version = "0.3.28", features = ["formatting", "local-offset", "macros"] }
tracing = { version = "0.1.37", features = ["max_level_trace", "release_max_level_debug"] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

View File

@ -50,7 +50,7 @@ You can also autostart systemd services like [mako] by symlinking them into `$HO
Niri also somewhat-works with xdg-desktop-portal-gnome for Flatpak apps.
## Hotkeys
## Default Hotkeys
When running on a TTY, the Mod key is <kbd>Super</kbd>.
When running in a window, the Mod key is <kbd>Alt</kbd>.
@ -60,11 +60,9 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
| Hotkey | Description |
| ------ | ----------- |
| <kbd>Mod</kbd><kbd>T</kbd> | Spawn `alacritty` |
| <kbd>Mod</kbd><kbd>D</kbd> | Spawn `fuzzel` |
| <kbd>Mod</kbd><kbd>N</kbd> | Spawn `nautilus` |
| <kbd>Mod</kbd><kbd>Q</kbd> | Close the focused window |
| <kbd>Mod</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd></kbd> | Focus the window to the left |
| <kbd>Mod</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd></kbd> | Focus the window to the right |
| <kbd>Mod</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd></kbd> | Focus the column to the left |
| <kbd>Mod</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd></kbd> | Focus the column to the right |
| <kbd>Mod</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd></kbd> | Focus the window below in a column |
| <kbd>Mod</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd></kbd> | Focus the window above in a column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd></kbd> | Move the focused column to the left |
@ -86,6 +84,12 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>T</kbd> | Toggle debug tinting of rendered elements |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>E</kbd> | Exit niri |
## Configuration
Niri will load configuration from `$XDG_CONFIG_HOME/.config/niri/config.kdl` or `~/.config/niri/config.kdl`.
If this fails, it will load [the default configuration file](resources/default-config.kdl).
Please use the default configuration file as the starting point for your custom configuration.
[PaperWM]: https://github.com/paperwm/PaperWM
[mako]: https://github.com/emersion/mako

View File

@ -0,0 +1,88 @@
// This config is in the KDL format: https://kdl.dev
// "/-" comments out the following node.
input {
keyboard {
xkb {
// You can set rules, model, layout, variant and options.
// For more information, see xkeyboard-config(7).
// For example:
/-layout "us,ru"
/-options "grp:win_space_toggle,compose:ralt,ctrl:nocaps"
}
}
// Next sections contain libinput settings.
// Omitting settings disables them, or leaves them at their default values.
touchpad {
tap
natural-scroll
/-accel-speed 0.2
}
}
binds {
// Keys consist of modifiers separated by + signs, followed by an XKB key name
// in the end. To find an XKB name for a particular key, you may use a program
// like wev.
//
// "Mod" is a special modifier equal to Super when running on a TTY, and to Alt
// when running as a winit window.
Mod+T { spawn "alacritty"; }
Mod+Q { close-window; }
Mod+H { focus-column-left; }
Mod+J { focus-window-down; }
Mod+K { focus-window-up; }
Mod+L { focus-column-right; }
Mod+Left { focus-column-left; }
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
Mod+Right { focus-column-right; }
Mod+Ctrl+H { move-column-left; }
Mod+Ctrl+J { move-window-down; }
Mod+Ctrl+K { move-window-up; }
Mod+Ctrl+L { move-column-right; }
Mod+Ctrl+Left { move-column-left; }
Mod+Ctrl+Down { move-window-down; }
Mod+Ctrl+Up { move-window-up; }
Mod+Ctrl+Right { move-column-right; }
Mod+Shift+H { focus-monitor-left; }
Mod+Shift+J { focus-monitor-down; }
Mod+Shift+K { focus-monitor-up; }
Mod+Shift+L { focus-monitor-right; }
Mod+Shift+Left { focus-monitor-left; }
Mod+Shift+Down { focus-monitor-down; }
Mod+Shift+Up { focus-monitor-up; }
Mod+Shift+Right { focus-monitor-right; }
Mod+Shift+Ctrl+H { move-window-to-monitor-left; }
Mod+Shift+Ctrl+J { move-window-to-monitor-down; }
Mod+Shift+Ctrl+K { move-window-to-monitor-up; }
Mod+Shift+Ctrl+L { move-window-to-monitor-right; }
Mod+Shift+Ctrl+Left { move-window-to-monitor-left; }
Mod+Shift+Ctrl+Down { move-window-to-monitor-down; }
Mod+Shift+Ctrl+Up { move-window-to-monitor-up; }
Mod+Shift+Ctrl+Right { move-window-to-monitor-right; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Ctrl+U { move-window-to-workspace-down; }
Mod+Ctrl+I { move-window-to-workspace-up; }
Mod+Comma { consume-window-into-column; }
Mod+Period { expel-window-from-column; }
Mod+R { switch-preset-column-width; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
Print { screenshot; }
Mod+Shift+E { quit; }
Mod+Shift+Ctrl+T { toggle-debug-tint; }
}

297
src/config.rs Normal file
View File

@ -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();
}
}

View File

@ -17,45 +17,10 @@ use smithay::input::pointer::{
use smithay::utils::SERIAL_COUNTER;
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
use crate::config::{Action, Config, Modifiers};
use crate::niri::State;
use crate::utils::get_monotonic_time;
enum Action {
None,
Quit,
ChangeVt(i32),
Suspend,
ToggleDebugTint,
Spawn(String),
Screenshot,
CloseWindow,
ToggleFullscreen,
FocusLeft,
FocusRight,
FocusDown,
FocusUp,
MoveLeft,
MoveRight,
MoveDown,
MoveUp,
ConsumeIntoColumn,
ExpelFromColumn,
SwitchWorkspaceDown,
SwitchWorkspaceUp,
MoveToWorkspaceDown,
MoveToWorkspaceUp,
FocusMonitorLeft,
FocusMonitorRight,
FocusMonitorDown,
FocusMonitorUp,
MoveToMonitorLeft,
MoveToMonitorRight,
MoveToMonitorDown,
MoveToMonitorUp,
ToggleWidth,
ToggleFullWidth,
}
pub enum CompositorMod {
Super,
Alt,
@ -70,13 +35,17 @@ impl From<Action> for FilterResult<Action> {
}
}
fn action(comp_mod: CompositorMod, keysym: KeysymHandle, mods: ModifiersState) -> Action {
fn action(
config: &Config,
comp_mod: CompositorMod,
keysym: KeysymHandle,
mods: ModifiersState,
) -> Action {
use keysyms::*;
let modified = keysym.modified_sym();
// Handle hardcoded binds.
#[allow(non_upper_case_globals)] // wat
match modified {
match keysym.modified_sym() {
modified @ KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12 => {
let vt = (modified - KEY_XF86Switch_VT_1 + 1) as i32;
return Action::ChangeVt(vt);
@ -85,59 +54,45 @@ fn action(comp_mod: CompositorMod, keysym: KeysymHandle, mods: ModifiersState) -
_ => (),
}
let mod_down = match comp_mod {
CompositorMod::Super => mods.logo,
CompositorMod::Alt => mods.alt,
// Handle configured binds.
let mut modifiers = Modifiers::empty();
if mods.ctrl {
modifiers |= Modifiers::CTRL;
}
if mods.shift {
modifiers |= Modifiers::SHIFT;
}
if mods.alt {
modifiers |= Modifiers::ALT;
}
if mods.logo {
modifiers |= Modifiers::SUPER;
}
let (mod_down, mut comp_mod) = match comp_mod {
CompositorMod::Super => (mods.logo, Modifiers::SUPER),
CompositorMod::Alt => (mods.alt, Modifiers::ALT),
};
if mod_down {
modifiers |= Modifiers::COMPOSITOR;
} else {
comp_mod = Modifiers::empty();
}
if !mod_down {
let Some(&raw) = keysym.raw_syms().first() else {
return Action::None;
};
for bind in &config.binds.0 {
if bind.key.keysym != raw {
continue;
}
if bind.key.modifiers | comp_mod == modifiers {
return bind.actions.first().cloned().unwrap_or(Action::None);
}
}
// FIXME: these don't work in the Russian layout. I guess I'll need to
// find a US keymap, then map keys somehow.
#[allow(non_upper_case_globals)] // wat
match modified {
KEY_E => Action::Quit,
KEY_t => Action::Spawn("alacritty".to_owned()),
KEY_d => Action::Spawn("fuzzel".to_owned()),
KEY_n => Action::Spawn("nautilus".to_owned()),
// Alt + PrtSc = SysRq
KEY_Sys_Req | KEY_Print => Action::Screenshot,
KEY_T if mods.shift && mods.ctrl => Action::ToggleDebugTint,
KEY_q => Action::CloseWindow,
KEY_F => Action::ToggleFullscreen,
KEY_comma => Action::ConsumeIntoColumn,
KEY_period => Action::ExpelFromColumn,
KEY_r => Action::ToggleWidth,
KEY_f => Action::ToggleFullWidth,
// Move to monitor.
KEY_H | KEY_Left if mods.shift && mods.ctrl => Action::MoveToMonitorLeft,
KEY_L | KEY_Right if mods.shift && mods.ctrl => Action::MoveToMonitorRight,
KEY_J | KEY_Down if mods.shift && mods.ctrl => Action::MoveToMonitorDown,
KEY_K | KEY_Up if mods.shift && mods.ctrl => Action::MoveToMonitorUp,
// Focus monitor.
KEY_H | KEY_Left if mods.shift => Action::FocusMonitorLeft,
KEY_L | KEY_Right if mods.shift => Action::FocusMonitorRight,
KEY_J | KEY_Down if mods.shift => Action::FocusMonitorDown,
KEY_K | KEY_Up if mods.shift => Action::FocusMonitorUp,
// Move.
KEY_h | KEY_Left if mods.ctrl => Action::MoveLeft,
KEY_l | KEY_Right if mods.ctrl => Action::MoveRight,
KEY_j | KEY_Down if mods.ctrl => Action::MoveDown,
KEY_k | KEY_Up if mods.ctrl => Action::MoveUp,
// Focus.
KEY_h | KEY_Left => Action::FocusLeft,
KEY_l | KEY_Right => Action::FocusRight,
KEY_j | KEY_Down => Action::FocusDown,
KEY_k | KEY_Up => Action::FocusUp,
// Workspaces.
KEY_u if mods.ctrl => Action::MoveToWorkspaceDown,
KEY_i if mods.ctrl => Action::MoveToWorkspaceUp,
KEY_u => Action::SwitchWorkspaceDown,
KEY_i => Action::SwitchWorkspaceUp,
_ => Action::None,
}
Action::None
}
impl State {
@ -166,9 +121,9 @@ impl State {
event.state(),
serial,
time,
|_, mods, keysym| {
|self_, mods, keysym| {
if event.state() == KeyState::Pressed {
action(comp_mod, keysym, *mods).into()
action(&self_.config, comp_mod, keysym, *mods).into()
} else {
FilterResult::Forward
}
@ -192,8 +147,10 @@ impl State {
self.backend.toggle_debug_tint();
}
Action::Spawn(command) => {
if let Err(err) = Command::new(command).spawn() {
warn!("error spawning alacritty: {err}");
if let Some((command, args)) = command.split_first() {
if let Err(err) = Command::new(command).args(args).spawn() {
warn!("error spawning {command}: {err}");
}
}
}
Action::Screenshot => {
@ -211,78 +168,78 @@ impl State {
window.toplevel().send_close();
}
}
Action::ToggleFullscreen => {
Action::FullscreenWindow => {
let focus = self.niri.monitor_set.focus().cloned();
if let Some(window) = focus {
self.niri.monitor_set.toggle_fullscreen(&window);
}
}
Action::MoveLeft => {
Action::MoveColumnLeft => {
self.niri.monitor_set.move_left();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveRight => {
Action::MoveColumnRight => {
self.niri.monitor_set.move_right();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveDown => {
Action::MoveWindowDown => {
self.niri.monitor_set.move_down();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveUp => {
Action::MoveWindowUp => {
self.niri.monitor_set.move_up();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusLeft => {
Action::FocusColumnLeft => {
self.niri.monitor_set.focus_left();
}
Action::FocusRight => {
Action::FocusColumnRight => {
self.niri.monitor_set.focus_right();
}
Action::FocusDown => {
Action::FocusWindowDown => {
self.niri.monitor_set.focus_down();
}
Action::FocusUp => {
Action::FocusWindowUp => {
self.niri.monitor_set.focus_up();
}
Action::MoveToWorkspaceDown => {
Action::MoveWindowToWorkspaceDown => {
self.niri.monitor_set.move_to_workspace_down();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveToWorkspaceUp => {
Action::MoveWindowToWorkspaceUp => {
self.niri.monitor_set.move_to_workspace_up();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::SwitchWorkspaceDown => {
Action::FocusWorkspaceDown => {
self.niri.monitor_set.switch_workspace_down();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::SwitchWorkspaceUp => {
Action::FocusWorkspaceUp => {
self.niri.monitor_set.switch_workspace_up();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ConsumeIntoColumn => {
Action::ConsumeWindowIntoColumn => {
self.niri.monitor_set.consume_into_column();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ExpelFromColumn => {
Action::ExpelWindowFromColumn => {
self.niri.monitor_set.expel_from_column();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ToggleWidth => {
Action::SwitchPresetColumnWidth => {
self.niri.monitor_set.toggle_width();
}
Action::ToggleFullWidth => {
Action::MaximizeColumn => {
self.niri.monitor_set.toggle_full_width();
}
Action::FocusMonitorLeft => {
@ -309,25 +266,25 @@ impl State {
self.move_cursor_to_output(&output);
}
}
Action::MoveToMonitorLeft => {
Action::MoveWindowToMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.monitor_set.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveToMonitorRight => {
Action::MoveWindowToMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.monitor_set.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveToMonitorDown => {
Action::MoveWindowToMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.monitor_set.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveToMonitorUp => {
Action::MoveWindowToMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.monitor_set.move_to_output(&output);
self.move_cursor_to_output(&output);
@ -740,9 +697,10 @@ impl State {
// According to Mutter code, this setting is specific to touchpads.
let is_touchpad = device.config_tap_finger_count() > 0;
if is_touchpad {
let _ = device.config_tap_set_enabled(true);
let _ = device.config_scroll_set_natural_scroll_enabled(true);
let _ = device.config_accel_set_speed(0.2);
let c = &self.config.input.touchpad;
let _ = device.config_tap_set_enabled(c.tap);
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
let _ = device.config_accel_set_speed(c.accel_speed);
}
}
}

View File

@ -3,6 +3,7 @@ extern crate tracing;
mod animation;
mod backend;
mod config;
mod dbus;
mod frame_clock;
mod handlers;
@ -13,8 +14,11 @@ mod utils;
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use clap::Parser;
use config::Config;
use miette::Context;
use niri::{Niri, State};
use smithay::reexports::calloop::EventLoop;
use smithay::reexports::wayland_server::Display;
@ -23,6 +27,9 @@ use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Path to config file (default: `$XDG_CONFIG_HOME/niri/config.kdl`).
#[arg(short, long)]
config: Option<PathBuf>,
/// Command to run upon compositor startup.
#[arg(last = true)]
command: Vec<OsString>,
@ -47,9 +54,22 @@ fn main() {
let _client = tracy_client::Client::start();
let config = match Config::load(cli.config).context("error loading config") {
Ok(config) => config,
Err(err) => {
warn!("{err:?}");
Config::default()
}
};
let mut event_loop = EventLoop::try_new().unwrap();
let mut display = Display::new().unwrap();
let state = State::new(event_loop.handle(), event_loop.get_signal(), &mut display);
let state = State::new(
config,
event_loop.handle(),
event_loop.get_signal(),
&mut display,
);
let mut data = LoopData { display, state };
if let Some((command, args)) = cli.command.split_first() {

View File

@ -56,6 +56,7 @@ use smithay::wayland::tablet_manager::TabletManagerState;
use time::OffsetDateTime;
use crate::backend::{Backend, Tty, Winit};
use crate::config::Config;
use crate::dbus::mutter_service_channel::ServiceChannel;
use crate::frame_clock::FrameClock;
use crate::layout::{MonitorRenderElement, MonitorSet};
@ -115,12 +116,14 @@ pub struct OutputState {
}
pub struct State {
pub config: Config,
pub backend: Backend,
pub niri: Niri,
}
impl State {
pub fn new(
config: Config,
event_loop: LoopHandle<'static, LoopData>,
stop_signal: LoopSignal,
display: &mut Display<State>,
@ -134,10 +137,20 @@ impl State {
Backend::Tty(Tty::new(event_loop.clone()))
};
let mut niri = Niri::new(event_loop, stop_signal, display, backend.seat_name());
let mut niri = Niri::new(
&config,
event_loop,
stop_signal,
display,
backend.seat_name(),
);
backend.init(&mut niri);
Self { backend, niri }
Self {
config,
backend,
niri,
}
}
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
@ -178,6 +191,7 @@ impl State {
impl Niri {
pub fn new(
config: &Config,
event_loop: LoopHandle<'static, LoopData>,
stop_signal: LoopSignal,
display: &mut Display<State>,
@ -202,11 +216,12 @@ impl Niri {
PresentationState::new::<State>(&display_handle, CLOCK_MONOTONIC as u32);
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, seat_name);
// FIXME: get Xkb and repeat interval from GNOME dconf.
let xkb = XkbConfig {
layout: "us,ru",
options: Some("grp:win_space_toggle,compose:ralt,ctrl:nocaps".to_owned()),
..Default::default()
rules: &config.input.keyboard.xkb.rules,
model: &config.input.keyboard.xkb.model,
layout: &config.input.keyboard.xkb.layout.as_deref().unwrap_or("us"),
variant: &config.input.keyboard.xkb.variant,
options: config.input.keyboard.xkb.options.clone(),
};
seat.add_keyboard(xkb, 400, 30).unwrap();
seat.add_pointer();