1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.displayManager;
10
11 installedSessions =
12 pkgs.runCommand "desktops"
13 {
14 # trivial derivation
15 preferLocalBuild = true;
16 allowSubstitutes = false;
17 }
18 ''
19 mkdir -p "$out/share/"{xsessions,wayland-sessions}
20
21 ${lib.concatMapStrings (pkg: ''
22 for n in ${lib.concatStringsSep " " pkg.providedSessions}; do
23 if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \
24 -f ${pkg}/share/xsessions/$n.desktop; then
25 echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:"
26 echo " ${pkg}"
27 return 1
28 fi
29 done
30
31 if test -d ${pkg}/share/xsessions; then
32 ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
33 fi
34 if test -d ${pkg}/share/wayland-sessions; then
35 ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions
36 fi
37 '') cfg.sessionPackages}
38 '';
39in
40{
41 options = {
42 services.displayManager = {
43 enable = lib.mkEnableOption "systemd's display-manager service";
44
45 preStart = lib.mkOption {
46 type = lib.types.lines;
47 default = "";
48 example = "rm -f /var/log/my-display-manager.log";
49 description = "Script executed before the display manager is started.";
50 };
51
52 execCmd = lib.mkOption {
53 type = lib.types.str;
54 example = lib.literalExpression ''"''${pkgs.lightdm}/bin/lightdm"'';
55 description = "Command to start the display manager.";
56 };
57
58 environment = lib.mkOption {
59 type = with lib.types; attrsOf unspecified;
60 default = { };
61 description = "Additional environment variables needed by the display manager.";
62 };
63
64 hiddenUsers = lib.mkOption {
65 type = with lib.types; listOf str;
66 default = [ "nobody" ];
67 description = ''
68 A list of users which will not be shown in the display manager.
69 '';
70 };
71
72 logToFile = lib.mkOption {
73 type = lib.types.bool;
74 default = false;
75 description = ''
76 Whether the display manager redirects the output of the
77 session script to {file}`~/.xsession-errors`.
78 '';
79 };
80
81 logToJournal = lib.mkOption {
82 type = lib.types.bool;
83 default = true;
84 description = ''
85 Whether the display manager redirects the output of the
86 session script to the systemd journal.
87 '';
88 };
89
90 # Configuration for automatic login. Common for all DM.
91 autoLogin = lib.mkOption {
92 type = lib.types.submodule (
93 { config, options, ... }:
94 {
95 options = {
96 enable = lib.mkOption {
97 type = lib.types.bool;
98 default = config.user != null;
99 defaultText = lib.literalExpression "config.${options.user} != null";
100 description = ''
101 Automatically log in as {option}`autoLogin.user`.
102 '';
103 };
104
105 user = lib.mkOption {
106 type = with lib.types; nullOr str;
107 default = null;
108 description = ''
109 User to be used for the automatic login.
110 '';
111 };
112 };
113 }
114 );
115
116 default = { };
117 description = ''
118 Auto login configuration attrset.
119 '';
120 };
121
122 defaultSession = lib.mkOption {
123 type = lib.types.nullOr lib.types.str // {
124 description = "session name";
125 check =
126 d:
127 lib.assertMsg (d != null -> (lib.types.str.check d && lib.elem d cfg.sessionData.sessionNames)) ''
128 Default graphical session, '${d}', not found.
129 Valid names for 'services.displayManager.defaultSession' are:
130 ${lib.concatStringsSep "\n " cfg.sessionData.sessionNames}
131 '';
132 };
133 default = null;
134 example = "gnome";
135 description = ''
136 Graphical session to pre-select in the session chooser (only effective for GDM, LightDM and SDDM).
137
138 On GDM, LightDM and SDDM, it will also be used as a session for auto-login.
139
140 Set this option to empty string to get an error with a list of currently available sessions.
141 '';
142 };
143
144 sessionData = lib.mkOption {
145 description = "Data exported for display managers’ convenience";
146 internal = true;
147 default = { };
148 };
149
150 sessionPackages = lib.mkOption {
151 type = lib.types.listOf (
152 lib.types.package
153 // {
154 description = "package with provided sessions";
155 check =
156 p:
157 lib.assertMsg
158 (
159 lib.types.package.check p
160 && p ? providedSessions
161 && p.providedSessions != [ ]
162 && lib.all lib.isString p.providedSessions
163 )
164 ''
165 Package, '${p.name}', did not specify any session names, as strings, in
166 'passthru.providedSessions'. This is required when used as a session package.
167
168 The session names can be looked up in:
169 ${p}/share/xsessions
170 ${p}/share/wayland-sessions
171 '';
172 }
173 );
174 default = [ ];
175 description = ''
176 A list of packages containing x11 or wayland session files to be passed to the display manager.
177 '';
178 };
179 };
180 };
181
182 imports = [
183 (lib.mkRenamedOptionModule
184 [ "services" "xserver" "displayManager" "autoLogin" ]
185 [ "services" "displayManager" "autoLogin" ]
186 )
187 (lib.mkRenamedOptionModule
188 [ "services" "xserver" "displayManager" "defaultSession" ]
189 [ "services" "displayManager" "defaultSession" ]
190 )
191 (lib.mkRenamedOptionModule
192 [ "services" "xserver" "displayManager" "hiddenUsers" ]
193 [ "services" "displayManager" "hiddenUsers" ]
194 )
195 (lib.mkRenamedOptionModule
196 [ "services" "xserver" "displayManager" "job" "environment" ]
197 [ "services" "displayManager" "environment" ]
198 )
199 (lib.mkRenamedOptionModule
200 [ "services" "xserver" "displayManager" "job" "execCmd" ]
201 [ "services" "displayManager" "execCmd" ]
202 )
203 (lib.mkRenamedOptionModule
204 [ "services" "xserver" "displayManager" "job" "logToFile" ]
205 [ "services" "displayManager" "logToFile" ]
206 )
207 (lib.mkRenamedOptionModule
208 [ "services" "xserver" "displayManager" "job" "logToJournal" ]
209 [ "services" "displayManager" "logToJournal" ]
210 )
211 (lib.mkRenamedOptionModule
212 [ "services" "xserver" "displayManager" "job" "preStart" ]
213 [ "services" "displayManager" "preStart" ]
214 )
215 (lib.mkRenamedOptionModule
216 [ "services" "xserver" "displayManager" "sessionData" ]
217 [ "services" "displayManager" "sessionData" ]
218 )
219 (lib.mkRenamedOptionModule
220 [ "services" "xserver" "displayManager" "sessionPackages" ]
221 [ "services" "displayManager" "sessionPackages" ]
222 )
223 ];
224
225 config = lib.mkIf cfg.enable {
226 assertions = [
227 {
228 assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
229 message = ''
230 services.displayManager.autoLogin.enable requires services.displayManager.autoLogin.user to be set
231 '';
232 }
233 ];
234
235 # Make xsessions and wayland sessions available in XDG_DATA_DIRS
236 # as some programs have behavior that depends on them being present
237 environment.sessionVariables.XDG_DATA_DIRS = lib.mkIf (cfg.sessionPackages != [ ]) [
238 "${cfg.sessionData.desktops}/share"
239 ];
240
241 services.displayManager.sessionData = {
242 desktops = installedSessions;
243 sessionNames = lib.concatMap (p: p.providedSessions) cfg.sessionPackages;
244 # We do not want to force users to set defaultSession when they have only single DE.
245 autologinSession =
246 if cfg.defaultSession != null then
247 cfg.defaultSession
248 else if cfg.sessionData.sessionNames != [ ] then
249 lib.head cfg.sessionData.sessionNames
250 else
251 null;
252 };
253
254 # so that the service won't be enabled when only startx is used
255 systemd.services.display-manager.enable =
256 let
257 dmConf = config.services.xserver.displayManager;
258 noDmUsed =
259 !(
260 dmConf.gdm.enable || cfg.sddm.enable || dmConf.xpra.enable || dmConf.lightdm.enable || cfg.ly.enable
261 );
262 in
263 lib.mkIf noDmUsed (lib.mkDefault false);
264
265 systemd.services.display-manager = {
266 description = "Display Manager";
267 after = [
268 "acpid.service"
269 "systemd-logind.service"
270 "systemd-user-sessions.service"
271 ];
272 restartIfChanged = false;
273
274 environment = cfg.environment;
275
276 preStart = cfg.preStart;
277 script = lib.mkIf (config.systemd.services.display-manager.enable == true) cfg.execCmd;
278
279 # Stop restarting if the display manager stops (crashes) 2 times
280 # in one minute. Starting X typically takes 3-4s.
281 startLimitIntervalSec = 30;
282 startLimitBurst = 3;
283 serviceConfig = {
284 Restart = "always";
285 RestartSec = "200ms";
286 SyslogIdentifier = "display-manager";
287 };
288 };
289 };
290}