1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 xcfg = config.services.xserver;
10 dmcfg = config.services.displayManager;
11 cfg = config.services.displayManager.sddm;
12 xEnv = config.systemd.services.display-manager.environment;
13
14 sddm = cfg.package.override (old: {
15 withWayland = cfg.wayland.enable;
16 withLayerShellQt = cfg.wayland.compositor == "kwin";
17 extraPackages = old.extraPackages or [ ] ++ cfg.extraPackages;
18 });
19
20 iniFmt = pkgs.formats.ini { };
21
22 inherit (lib)
23 concatMapStrings
24 concatStringsSep
25 getExe
26 attrNames
27 getAttr
28 optionalAttrs
29 optionalString
30 mkRemovedOptionModule
31 mkRenamedOptionModule
32 mkIf
33 mkEnableOption
34 mkOption
35 mkPackageOption
36 types
37 ;
38
39 xserverWrapper = pkgs.writeShellScript "xserver-wrapper" ''
40 ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
41 exec systemd-cat -t xserver-wrapper ${xcfg.displayManager.xserverBin} ${toString xcfg.displayManager.xserverArgs} "$@"
42 '';
43
44 Xsetup = pkgs.writeShellScript "Xsetup" ''
45 ${cfg.setupScript}
46 ${xcfg.displayManager.setupCommands}
47 '';
48
49 Xstop = pkgs.writeShellScript "Xstop" ''
50 ${cfg.stopScript}
51 '';
52
53 defaultConfig =
54 {
55 General =
56 {
57 HaltCommand = "/run/current-system/systemd/bin/systemctl poweroff";
58 RebootCommand = "/run/current-system/systemd/bin/systemctl reboot";
59 Numlock = if cfg.autoNumlock then "on" else "none"; # on, off none
60
61 # Implementation is done via pkgs/applications/display-managers/sddm/sddm-default-session.patch
62 DefaultSession = optionalString (
63 config.services.displayManager.defaultSession != null
64 ) "${config.services.displayManager.defaultSession}.desktop";
65
66 DisplayServer = if cfg.wayland.enable then "wayland" else "x11";
67 }
68 // optionalAttrs (cfg.wayland.enable && cfg.wayland.compositor == "kwin") {
69 GreeterEnvironment = "QT_WAYLAND_SHELL_INTEGRATION=layer-shell";
70 InputMethod = ""; # needed if we are using --inputmethod with kwin
71 };
72
73 Theme =
74 {
75 Current = cfg.theme;
76 ThemeDir = "/run/current-system/sw/share/sddm/themes";
77 FacesDir = "/run/current-system/sw/share/sddm/faces";
78 }
79 // optionalAttrs (cfg.theme == "breeze") {
80 CursorTheme = "breeze_cursors";
81 CursorSize = 24;
82 };
83
84 Users = {
85 MaximumUid = config.ids.uids.nixbld;
86 HideUsers = concatStringsSep "," dmcfg.hiddenUsers;
87 HideShells = "/run/current-system/sw/bin/nologin";
88 };
89
90 Wayland = {
91 EnableHiDPI = cfg.enableHidpi;
92 SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
93 CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand;
94 };
95
96 }
97 // optionalAttrs xcfg.enable {
98 X11 = {
99 MinimumVT = if xcfg.tty != null then xcfg.tty else 7;
100 ServerPath = toString xserverWrapper;
101 XephyrPath = "${pkgs.xorg.xorgserver.out}/bin/Xephyr";
102 SessionCommand = toString dmcfg.sessionData.wrapper;
103 SessionDir = "${dmcfg.sessionData.desktops}/share/xsessions";
104 XauthPath = "${pkgs.xorg.xauth}/bin/xauth";
105 DisplayCommand = toString Xsetup;
106 DisplayStopCommand = toString Xstop;
107 EnableHiDPI = cfg.enableHidpi;
108 };
109 }
110 // optionalAttrs dmcfg.autoLogin.enable {
111 Autologin = {
112 User = dmcfg.autoLogin.user;
113 Session = autoLoginSessionName;
114 Relogin = cfg.autoLogin.relogin;
115 };
116 };
117
118 cfgFile = iniFmt.generate "sddm.conf" (lib.recursiveUpdate defaultConfig cfg.settings);
119
120 autoLoginSessionName = "${dmcfg.sessionData.autologinSession}.desktop";
121
122 compositorCmds = {
123 kwin = concatStringsSep " " [
124 "${lib.getBin pkgs.kdePackages.kwin}/bin/kwin_wayland"
125 "--no-global-shortcuts"
126 "--no-kactivities"
127 "--no-lockscreen"
128 "--locale1"
129 ];
130 # This is basically the upstream default, but with Weston referenced by full path
131 # and the configuration generated from NixOS options.
132 weston =
133 let
134 westonIni = (pkgs.formats.ini { }).generate "weston.ini" {
135 libinput = {
136 enable-tap = config.services.libinput.mouse.tapping;
137 left-handed = config.services.libinput.mouse.leftHanded;
138 };
139 keyboard = {
140 keymap_model = xcfg.xkb.model;
141 keymap_layout = xcfg.xkb.layout;
142 keymap_variant = xcfg.xkb.variant;
143 keymap_options = xcfg.xkb.options;
144 };
145 };
146 in
147 "${getExe pkgs.weston} --shell=kiosk -c ${westonIni}";
148 };
149
150in
151{
152 imports = [
153 (mkRenamedOptionModule
154 [ "services" "xserver" "displayManager" "sddm" "autoLogin" "minimumUid" ]
155 [ "services" "displayManager" "sddm" "autoLogin" "minimumUid" ]
156 )
157 (mkRenamedOptionModule
158 [ "services" "xserver" "displayManager" "sddm" "autoLogin" "relogin" ]
159 [ "services" "displayManager" "sddm" "autoLogin" "relogin" ]
160 )
161 (mkRenamedOptionModule
162 [ "services" "xserver" "displayManager" "sddm" "autoNumlock" ]
163 [ "services" "displayManager" "sddm" "autoNumlock" ]
164 )
165 (mkRenamedOptionModule
166 [ "services" "xserver" "displayManager" "sddm" "enable" ]
167 [ "services" "displayManager" "sddm" "enable" ]
168 )
169 (mkRenamedOptionModule
170 [ "services" "xserver" "displayManager" "sddm" "enableHidpi" ]
171 [ "services" "displayManager" "sddm" "enableHidpi" ]
172 )
173 (mkRenamedOptionModule
174 [ "services" "xserver" "displayManager" "sddm" "extraPackages" ]
175 [ "services" "displayManager" "sddm" "extraPackages" ]
176 )
177 (mkRenamedOptionModule
178 [ "services" "xserver" "displayManager" "sddm" "package" ]
179 [ "services" "displayManager" "sddm" "package" ]
180 )
181 (mkRenamedOptionModule
182 [ "services" "xserver" "displayManager" "sddm" "settings" ]
183 [ "services" "displayManager" "sddm" "settings" ]
184 )
185 (mkRenamedOptionModule
186 [ "services" "xserver" "displayManager" "sddm" "setupScript" ]
187 [ "services" "displayManager" "sddm" "setupScript" ]
188 )
189 (mkRenamedOptionModule
190 [ "services" "xserver" "displayManager" "sddm" "stopScript" ]
191 [ "services" "displayManager" "sddm" "stopScript" ]
192 )
193 (mkRenamedOptionModule
194 [ "services" "xserver" "displayManager" "sddm" "theme" ]
195 [ "services" "displayManager" "sddm" "theme" ]
196 )
197 (mkRenamedOptionModule
198 [ "services" "xserver" "displayManager" "sddm" "wayland" "enable" ]
199 [ "services" "displayManager" "sddm" "wayland" "enable" ]
200 )
201
202 (mkRemovedOptionModule [
203 "services"
204 "displayManager"
205 "sddm"
206 "themes"
207 ] "Set the option `services.displayManager.sddm.package' instead.")
208 (mkRenamedOptionModule
209 [ "services" "displayManager" "sddm" "autoLogin" "enable" ]
210 [ "services" "displayManager" "autoLogin" "enable" ]
211 )
212 (mkRenamedOptionModule
213 [ "services" "displayManager" "sddm" "autoLogin" "user" ]
214 [ "services" "displayManager" "autoLogin" "user" ]
215 )
216 (mkRemovedOptionModule [
217 "services"
218 "displayManager"
219 "sddm"
220 "extraConfig"
221 ] "Set the option `services.displayManager.sddm.settings' instead.")
222 ];
223
224 options = {
225
226 services.displayManager.sddm = {
227 enable = mkOption {
228 type = types.bool;
229 default = false;
230 description = ''
231 Whether to enable sddm as the display manager.
232 '';
233 };
234
235 package = mkPackageOption pkgs [ "plasma5Packages" "sddm" ] { };
236
237 enableHidpi = mkOption {
238 type = types.bool;
239 default = true;
240 description = ''
241 Whether to enable automatic HiDPI mode.
242 '';
243 };
244
245 settings = mkOption {
246 type = iniFmt.type;
247 default = { };
248 example = {
249 Autologin = {
250 User = "john";
251 Session = "plasma.desktop";
252 };
253 };
254 description = ''
255 Extra settings merged in and overwriting defaults in sddm.conf.
256 '';
257 };
258
259 theme = mkOption {
260 type = types.str;
261 default = "";
262 description = ''
263 Greeter theme to use.
264 '';
265 };
266
267 extraPackages = mkOption {
268 type = types.listOf types.package;
269 default = [ ];
270 defaultText = "[]";
271 description = ''
272 Extra Qt plugins / QML libraries to add to the environment.
273 '';
274 };
275
276 autoNumlock = mkOption {
277 type = types.bool;
278 default = false;
279 description = ''
280 Enable numlock at login.
281 '';
282 };
283
284 setupScript = mkOption {
285 type = types.str;
286 default = "";
287 example = ''
288 # workaround for using NVIDIA Optimus without Bumblebee
289 xrandr --setprovideroutputsource modesetting NVIDIA-0
290 xrandr --auto
291 '';
292 description = ''
293 A script to execute when starting the display server. DEPRECATED, please
294 use {option}`services.xserver.displayManager.setupCommands`.
295 '';
296 };
297
298 stopScript = mkOption {
299 type = types.str;
300 default = "";
301 description = ''
302 A script to execute when stopping the display server.
303 '';
304 };
305
306 # Configuration for automatic login specific to SDDM
307 autoLogin = {
308 relogin = mkOption {
309 type = types.bool;
310 default = false;
311 description = ''
312 If true automatic login will kick in again on session exit (logout), otherwise it
313 will only log in automatically when the display-manager is started.
314 '';
315 };
316
317 minimumUid = mkOption {
318 type = types.ints.u16;
319 default = 1000;
320 description = ''
321 Minimum user ID for auto-login user.
322 '';
323 };
324 };
325
326 # Experimental Wayland support
327 wayland = {
328 enable = mkEnableOption "experimental Wayland support";
329
330 compositor = mkOption {
331 description = "The compositor to use: ${lib.concatStringsSep ", " (builtins.attrNames compositorCmds)}";
332 type = types.enum (builtins.attrNames compositorCmds);
333 default = "weston";
334 };
335
336 compositorCommand = mkOption {
337 type = types.str;
338 internal = true;
339 default = compositorCmds.${cfg.wayland.compositor};
340 description = "Command used to start the selected compositor";
341 };
342 };
343 };
344 };
345
346 config = mkIf cfg.enable {
347
348 assertions = [
349 {
350 assertion = xcfg.enable || cfg.wayland.enable;
351 message = ''
352 SDDM requires either services.xserver.enable or services.displayManager.sddm.wayland.enable to be true
353 '';
354 }
355 {
356 assertion = config.services.displayManager.autoLogin.enable -> autoLoginSessionName != null;
357 message = ''
358 SDDM auto-login requires that services.displayManager.defaultSession is set.
359 '';
360 }
361 ];
362
363 services.displayManager = {
364 enable = true;
365 execCmd = "exec /run/current-system/sw/bin/sddm";
366 };
367
368 security.pam.services = {
369 sddm.text = ''
370 auth substack login
371 account include login
372 password substack login
373 session include login
374 '';
375
376 sddm-greeter.text = ''
377 auth required pam_succeed_if.so audit quiet_success user = sddm
378 auth optional pam_permit.so
379
380 account required pam_succeed_if.so audit quiet_success user = sddm
381 account sufficient pam_unix.so
382
383 password required pam_deny.so
384
385 session required pam_succeed_if.so audit quiet_success user = sddm
386 session required pam_env.so conffile=/etc/pam/environment readenv=0
387 session optional ${config.systemd.package}/lib/security/pam_systemd.so
388 session optional pam_keyinit.so force revoke
389 session optional pam_permit.so
390 '';
391
392 sddm-autologin.text = ''
393 auth requisite pam_nologin.so
394 auth required pam_succeed_if.so uid >= ${toString cfg.autoLogin.minimumUid} quiet
395 auth required pam_permit.so
396
397 account include sddm
398
399 password include sddm
400
401 session include sddm
402 '';
403 };
404
405 users.users.sddm = {
406 createHome = true;
407 home = "/var/lib/sddm";
408 group = "sddm";
409 uid = config.ids.uids.sddm;
410 };
411
412 environment = {
413 etc."sddm.conf".source = cfgFile;
414 pathsToLink = [
415 "/share/sddm"
416 ];
417 systemPackages = [ sddm ];
418 };
419
420 users.groups.sddm.gid = config.ids.gids.sddm;
421
422 services = {
423 dbus.packages = [ sddm ];
424 xserver = {
425 # To enable user switching, allow sddm to allocate TTYs/displays dynamically.
426 tty = null;
427 display = null;
428 };
429 };
430
431 systemd = {
432 tmpfiles.packages = [ sddm ];
433
434 # We're not using the upstream unit, so copy these: https://github.com/sddm/sddm/blob/develop/services/sddm.service.in
435 services.display-manager = {
436 after = [
437 "systemd-user-sessions.service"
438 "getty@tty7.service"
439 "plymouth-quit.service"
440 "systemd-logind.service"
441 ];
442 conflicts = [
443 "getty@tty7.service"
444 ];
445 };
446 };
447 };
448}