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