-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
# Copyright 2025 Shota FUJI <pockawoooh@gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
# SPDX-License-Identifier: 0BSD
#
# > Scrollable-tiling Wayland compositor
# https://github.com/YaLTeR/niri
{
config,
lib,
pkgs,
...
}:
let
cfg = config.wayland-desktop.niri;
output = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.nonEmptyStr;
description = "Display output name, you can obtain from `niri msg outputs`";
};
scale = lib.mkOption {
type = lib.types.float;
description = "DPI";
default = 1.0;
};
};
};
serializeOutput = o: ''
output "${o.name}" {
scale ${builtins.toString o.scale}
}
'';
serializeSpawnArg = a: builtins.concatStringsSep " " (builtins.map (s: "\"${s}\"") a);
light = config.palette.rose-pine.variants.${config.palette.rose-pine.light-variant};
dark = config.palette.rose-pine.variants.${config.palette.rose-pine.dark-variant};
# https://github.com/YaLTeR/niri/wiki/Configuration:-Overview
configFile =
{
backdropColor,
activeFocusRingColor,
inactiveFocusRingColor,
activeBorderColor,
inactiveBorderColor,
cursorTheme ? "default",
gap ? 16,
}:
''
input {
keyboard {
repeat-delay ${builtins.toString cfg.input.keyboard.repeat-delay}
repeat-rate ${builtins.toString cfg.input.keyboard.repeat-rate}
}
// This section includes libinput settings.
// Omitting settings disables them, or leaves them at their default values.
touchpad {
natural-scroll
accel-speed 0.2
accel-profile "adaptive"
scroll-method "two-finger"
scroll-factor 0.3
click-method "clickfinger"
}
}
${lib.strings.concatStringsSep "\n" (builtins.map serializeOutput cfg.outputs)}
overview {
backdrop-color "${backdropColor}"
workspace-shadow {
off
}
}
layout {
gaps ${builtins.toString gap}
center-focused-column "never"
background-color "transparent"
preset-column-widths {
// Proportion sets the width as a fraction of the output width, taking gaps into account.
// For example, you can perfectly fit four windows sized "proportion 0.25" on an output.
proportion 0.25
proportion 0.5
proportion 0.75
proportion 1.0
}
preset-window-heights {
proportion 0.25
proportion 0.5
proportion 0.75
proportion 1.0
}
// You can change the default width of the new windows.
// If you leave the brackets empty, the windows themselves will decide their initial width.
default-column-width {
proportion 0.5
}
focus-ring {
width 1
active-color "${activeFocusRingColor}"
inactive-color "${inactiveFocusRingColor}"
}
border {
width 1
active-color "${activeBorderColor}"
inactive-color "${inactiveBorderColor}"
}
shadow {
on
softness 8
spread -3
offset x=0 y=0
}
struts {
left 0
right 0
top 0
bottom ${builtins.toString (gap / -2)}
}
}
${if cfg.prefer-no-csd then "" else "//"}prefer-no-csd
screenshot-path "${cfg.screenshot-path}"
animations {
}
// Open the Firefox picture-in-picture player as floating by default.
window-rule {
// This app-id regular expression will work for both:
// - host Firefox (app-id is "firefox")
// - Flatpak Firefox (app-id is "org.mozilla.firefox")
match app-id=r#"firefox$"# title="^Picture-in-Picture$"
open-floating true
}
// Enable rounded corners for all windows.
window-rule {
geometry-corner-radius 5
clip-to-geometry true
}
hotkey-overlay {
${if cfg.hotkey-overlay.skip-at-startup then "" else "//"}skip-at-startup
}
${builtins.concatStringsSep "\n" (
builtins.map (a: "spawn-at-startup ${serializeSpawnArg a}") cfg.spawn-at-startup
)}
spawn-at-startup "${pkgs.waybar}/bin/waybar"
binds {
Mod+Shift+Slash { show-hotkey-overlay; }
Mod+T { spawn "${lib.getExe pkgs.wezterm}" "start"; }
Mod+X { spawn "swaylock"; }
Mod+Q { close-window; }
Mod+Space { spawn "${lib.getExe pkgs.anyrun}"; }
Mod+H { focus-column-left; }
Mod+J { focus-window-down-or-column-right; }
Mod+K { focus-window-up-or-column-left; }
Mod+L { focus-column-right; }
Mod+Ctrl+H { move-column-left; }
Mod+Ctrl+J { consume-or-expel-window-left; }
Mod+Ctrl+K { consume-or-expel-window-right; }
Mod+Ctrl+L { move-column-right; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Ctrl+U { move-column-to-workspace-down; }
Mod+Ctrl+I { move-column-to-workspace-up; }
Mod+BracketLeft { consume-or-expel-window-left; }
Mod+BracketRight { consume-or-expel-window-right; }
Mod+Comma { consume-window-into-column; }
Mod+Period { expel-window-from-column; }
Mod+R { switch-preset-column-width; }
Mod+Shift+R { switch-preset-column-width-back; }
Mod+V { switch-preset-window-height; }
Mod+Shift+V { switch-preset-window-height-back; }
Mod+Ctrl+R { reset-window-height; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
Mod+C { center-column; }
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
Mod+Shift+Minus { set-window-height "-10%"; }
Mod+Shift+Equal { set-window-height "+10%"; }
Mod+Shift+E { quit; }
Ctrl+Alt+Delete { quit; }
Mod+Shift+P { power-off-monitors; }
}
cursor {
xcursor-theme "${cursorTheme}"
}
'';
in
{
imports = [ ../palette/rose-pine.nix ];
options = {
wayland-desktop.niri = {
outputs = lib.mkOption {
type = lib.types.listOf output;
default = [
{
name = "LG Electronics LG HDR 4K 0x00018909";
scale = 1.4;
}
];
};
input = {
keyboard = {
repeat-delay = lib.mkOption {
type = lib.types.ints.unsigned;
default = 200;
};
repeat-rate = lib.mkOption {
type = lib.types.ints.unsigned;
default = 25;
};
};
};
spawn-at-startup = lib.mkOption {
type = lib.types.listOf (lib.types.listOf lib.types.nonEmptyStr);
description = ''
`spawn-at-startup` accepts a path to the program binary as the first argument, followed by arguments to the program.
Note that running niri as a systemd session supports xdg-desktop-autostart out of the box, which may be more convenient to use. Thanks to this, apps that you configured to autostart in GNOME will also "just work" in niri, without any manual `spawn-at-startup` configuration.
'';
default = [ ];
};
prefer-no-csd = lib.mkOption {
type = lib.types.bool;
description = ''
Ask the clients to omit their client-side decorations if possible.
If the client will specifically ask for CSD, the request will be honored.
Additionally, clients will be informed that they are tiled, removing some client-side rounded corners.
This option will also fix border/focus ring drawing behind some semitransparent windows.
After enabling or disabling this, you need to restart the apps for this to take effect.
'';
default = true;
};
screenshot-path = lib.mkOption {
type = lib.types.nonEmptyStr;
description = ''
You can change the path where screenshots are saved.
A ~ at the front will be expanded to the home directory.
The path is formatted with strftime(3) to give you the screenshot date and time.
'';
default = "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png";
};
hotkey-overlay = {
skip-at-startup = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
};
};
config = {
xdg.configFile."niri/dark.kdl" = {
text = configFile {
backdropColor = dark.base;
activeFocusRingColor = dark.rose;
inactiveFocusRingColor = dark.highlightLow;
activeBorderColor = dark.highlightHigh;
inactiveBorderColor = dark.highlightHigh;
cursorTheme = "BreezeX-RosePine-Linux";
};
};
xdg.configFile."niri/light.kdl" = {
text = configFile {
backdropColor = light.base;
activeFocusRingColor = light.rose;
inactiveFocusRingColor = light.highlightLow;
activeBorderColor = light.highlightHigh;
inactiveBorderColor = light.highlightHigh;
cursorTheme = "BreezeX-RosePineDawn-Linux";
};
};
services.darkman = {
lightModeScripts.niri = ''
ln -sf ${config.xdg.configHome}/niri/light.kdl ${config.xdg.configHome}/niri/config.kdl
'';
darkModeScripts.niri = ''
ln -sf ${config.xdg.configHome}/niri/dark.kdl ${config.xdg.configHome}/niri/config.kdl
'';
};
};
}