aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock268
-rw-r--r--Cargo.toml3
-rw-r--r--README.md14
-rw-r--r--resources/default-config.kdl88
-rw-r--r--src/config.rs297
-rw-r--r--src/input.rs190
-rw-r--r--src/main.rs22
-rw-r--r--src/niri.rs27
8 files changed, 781 insertions, 128 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d42787f7..d35694a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,12 +3,32 @@
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -245,6 +265,36 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -378,6 +428,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -845,6 +904,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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"
@@ -965,6 +1036,23 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1010,6 +1098,33 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1160,6 +1275,38 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1226,9 +1373,12 @@ dependencies = [
"directories",
"image",
"keyframe",
+ "knuffel",
"logind-zbus",
+ "miette",
"profiling",
"sd-notify",
+ "serde",
"smithay",
"smithay-drm-extras",
"time",
@@ -1406,6 +1556,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1443,6 +1602,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1518,6 +1683,30 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1674,6 +1863,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1825,6 +2020,12 @@ 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"
source = "git+https://github.com/Smithay/smithay.git#04f63f30caca9d7af1059fc4d72edeea47ac79ca"
@@ -1918,6 +2119,34 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1953,6 +2182,27 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2145,6 +2395,24 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 38ea6da4..165dfde7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
diff --git a/README.md b/README.md
index e7050be2..25f46605 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
new file mode 100644
index 00000000..00a73451
--- /dev/null
+++ b/resources/default-config.kdl
@@ -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; }
+}
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();
+ }
+}
diff --git a/src/input.rs b/src/input.rs
index 514dcd2c..ad83d331 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -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;
+ }
- // 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,
+ if bind.key.modifiers | comp_mod == modifiers {
+ return bind.actions.first().cloned().unwrap_or(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.