1{ config, lib, pkgs, ... }:
2
3with lib;
4let
5 xcfg = config.services.xserver;
6 dmcfg = xcfg.displayManager;
7 cfg = dmcfg.sddm;
8 xEnv = config.systemd.services.display-manager.environment;
9
10 sddm = pkgs.libsForQt5.sddm;
11
12 iniFmt = pkgs.formats.ini { };
13
14 xserverWrapper = pkgs.writeShellScript "xserver-wrapper" ''
15 ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
16 exec systemd-cat -t xserver-wrapper ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
17 '';
18
19 Xsetup = pkgs.writeShellScript "Xsetup" ''
20 ${cfg.setupScript}
21 ${dmcfg.setupCommands}
22 '';
23
24 Xstop = pkgs.writeShellScript "Xstop" ''
25 ${cfg.stopScript}
26 '';
27
28 defaultConfig = {
29 General = {
30 HaltCommand = "/run/current-system/systemd/bin/systemctl poweroff";
31 RebootCommand = "/run/current-system/systemd/bin/systemctl reboot";
32 Numlock = if cfg.autoNumlock then "on" else "none"; # on, off none
33 };
34
35 Theme = {
36 Current = cfg.theme;
37 ThemeDir = "/run/current-system/sw/share/sddm/themes";
38 FacesDir = "/run/current-system/sw/share/sddm/faces";
39 };
40
41 Users = {
42 MaximumUid = config.ids.uids.nixbld;
43 HideUsers = concatStringsSep "," dmcfg.hiddenUsers;
44 HideShells = "/run/current-system/sw/bin/nologin";
45 };
46
47 X11 = {
48 MinimumVT = if xcfg.tty != null then xcfg.tty else 7;
49 ServerPath = toString xserverWrapper;
50 XephyrPath = "${pkgs.xorg.xorgserver.out}/bin/Xephyr";
51 SessionCommand = toString dmcfg.sessionData.wrapper;
52 SessionDir = "${dmcfg.sessionData.desktops}/share/xsessions";
53 XauthPath = "${pkgs.xorg.xauth}/bin/xauth";
54 DisplayCommand = toString Xsetup;
55 DisplayStopCommand = toString Xstop;
56 EnableHiDPI = cfg.enableHidpi;
57 };
58
59 Wayland = {
60 EnableHiDPI = cfg.enableHidpi;
61 SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
62 };
63 } // lib.optionalAttrs dmcfg.autoLogin.enable {
64 Autologin = {
65 User = dmcfg.autoLogin.user;
66 Session = autoLoginSessionName;
67 Relogin = cfg.autoLogin.relogin;
68 };
69 };
70
71 cfgFile =
72 iniFmt.generate "sddm.conf" (lib.recursiveUpdate defaultConfig cfg.settings);
73
74 autoLoginSessionName =
75 "${dmcfg.sessionData.autologinSession}.desktop";
76
77in
78{
79 imports = [
80 (mkRemovedOptionModule
81 [ "services" "xserver" "displayManager" "sddm" "themes" ]
82 "Set the option `services.xserver.displayManager.sddm.package' instead.")
83 (mkRenamedOptionModule
84 [ "services" "xserver" "displayManager" "sddm" "autoLogin" "enable" ]
85 [ "services" "xserver" "displayManager" "autoLogin" "enable" ])
86 (mkRenamedOptionModule
87 [ "services" "xserver" "displayManager" "sddm" "autoLogin" "user" ]
88 [ "services" "xserver" "displayManager" "autoLogin" "user" ])
89 (mkRemovedOptionModule
90 [ "services" "xserver" "displayManager" "sddm" "extraConfig" ]
91 "Set the option `services.xserver.displayManager.sddm.settings' instead.")
92 ];
93
94 options = {
95
96 services.xserver.displayManager.sddm = {
97 enable = mkOption {
98 type = types.bool;
99 default = false;
100 description = ''
101 Whether to enable sddm as the display manager.
102 '';
103 };
104
105 enableHidpi = mkOption {
106 type = types.bool;
107 default = true;
108 description = ''
109 Whether to enable automatic HiDPI mode.
110 '';
111 };
112
113 settings = mkOption {
114 type = iniFmt.type;
115 default = { };
116 example = ''
117 {
118 Autologin = {
119 User = "john";
120 Session = "plasma.desktop";
121 };
122 }
123 '';
124 description = ''
125 Extra settings merged in and overwritting defaults in sddm.conf.
126 '';
127 };
128
129 theme = mkOption {
130 type = types.str;
131 default = "";
132 description = ''
133 Greeter theme to use.
134 '';
135 };
136
137 autoNumlock = mkOption {
138 type = types.bool;
139 default = false;
140 description = ''
141 Enable numlock at login.
142 '';
143 };
144
145 setupScript = mkOption {
146 type = types.str;
147 default = "";
148 example = ''
149 # workaround for using NVIDIA Optimus without Bumblebee
150 xrandr --setprovideroutputsource modesetting NVIDIA-0
151 xrandr --auto
152 '';
153 description = ''
154 A script to execute when starting the display server. DEPRECATED, please
155 use <option>services.xserver.displayManager.setupCommands</option>.
156 '';
157 };
158
159 stopScript = mkOption {
160 type = types.str;
161 default = "";
162 description = ''
163 A script to execute when stopping the display server.
164 '';
165 };
166
167 # Configuration for automatic login specific to SDDM
168 autoLogin = {
169 relogin = mkOption {
170 type = types.bool;
171 default = false;
172 description = ''
173 If true automatic login will kick in again on session exit (logout), otherwise it
174 will only log in automatically when the display-manager is started.
175 '';
176 };
177
178 minimumUid = mkOption {
179 type = types.ints.u16;
180 default = 1000;
181 description = ''
182 Minimum user ID for auto-login user.
183 '';
184 };
185 };
186 };
187 };
188
189 config = mkIf cfg.enable {
190
191 assertions = [
192 {
193 assertion = xcfg.enable;
194 message = ''
195 SDDM requires services.xserver.enable to be true
196 '';
197 }
198 {
199 assertion = dmcfg.autoLogin.enable -> autoLoginSessionName != null;
200 message = ''
201 SDDM auto-login requires that services.xserver.displayManager.defaultSession is set.
202 '';
203 }
204 ];
205
206 services.xserver.displayManager.job = {
207 environment = {
208 # Load themes from system environment
209 QT_PLUGIN_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtPluginPrefix;
210 QML2_IMPORT_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtQmlPrefix;
211 };
212
213 execCmd = "exec /run/current-system/sw/bin/sddm";
214 };
215
216 security.pam.services = {
217 sddm = {
218 allowNullPassword = true;
219 startSession = true;
220 };
221
222 sddm-greeter.text = ''
223 auth required pam_succeed_if.so audit quiet_success user = sddm
224 auth optional pam_permit.so
225
226 account required pam_succeed_if.so audit quiet_success user = sddm
227 account sufficient pam_unix.so
228
229 password required pam_deny.so
230
231 session required pam_succeed_if.so audit quiet_success user = sddm
232 session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
233 session optional ${pkgs.systemd}/lib/security/pam_systemd.so
234 session optional pam_keyinit.so force revoke
235 session optional pam_permit.so
236 '';
237
238 sddm-autologin.text = ''
239 auth requisite pam_nologin.so
240 auth required pam_succeed_if.so uid >= ${toString cfg.autoLogin.minimumUid} quiet
241 auth required pam_permit.so
242
243 account include sddm
244
245 password include sddm
246
247 session include sddm
248 '';
249 };
250
251 users.users.sddm = {
252 createHome = true;
253 home = "/var/lib/sddm";
254 group = "sddm";
255 uid = config.ids.uids.sddm;
256 };
257
258 environment.etc."sddm.conf".source = cfgFile;
259 environment.pathsToLink = [
260 "/share/sddm"
261 ];
262
263 users.groups.sddm.gid = config.ids.gids.sddm;
264
265 environment.systemPackages = [ sddm ];
266 services.dbus.packages = [ sddm ];
267
268 # To enable user switching, allow sddm to allocate TTYs/displays dynamically.
269 services.xserver.tty = null;
270 services.xserver.display = null;
271
272 systemd.tmpfiles.rules = [
273 # Prior to Qt 5.9.2, there is a QML cache invalidation bug which sometimes
274 # strikes new Plasma 5 releases. If the QML cache is not invalidated, SDDM
275 # will segfault without explanation. We really tore our hair out for awhile
276 # before finding the bug:
277 # https://bugreports.qt.io/browse/QTBUG-62302
278 # We work around the problem by deleting the QML cache before startup.
279 # This was supposedly fixed in Qt 5.9.2 however it has been reported with
280 # 5.10 and 5.11 as well. The initial workaround was to delete the directory
281 # in the Xsetup script but that doesn't do anything.
282 # Instead we use tmpfiles.d to ensure it gets wiped.
283 # This causes a small but perceptible delay when SDDM starts.
284 "e ${config.users.users.sddm.home}/.cache - - - 0"
285 ];
286 };
287}