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