1# This module declares the options to define a *display manager*, the
2# program responsible for handling X logins (such as xdm, gdb, or
3# SLiM). The display manager allows the user to select a *session
4# type*. When the user logs in, the display manager starts the
5# *session script* ("xsession" below) to launch the selected session
6# type. The session type defines two things: the *desktop manager*
7# (e.g., KDE, Gnome or a plain xterm), and optionally the *window
8# manager* (e.g. kwin or twm).
9
10{ config, lib, pkgs, ... }:
11
12with lib;
13
14let
15
16 cfg = config.services.xserver;
17 xorg = pkgs.xorg;
18
19 fontconfig = config.fonts.fontconfig;
20 xresourcesXft = pkgs.writeText "Xresources-Xft" ''
21 ${optionalString (fontconfig.dpi != 0) ''Xft.dpi: ${toString fontconfig.dpi}''}
22 Xft.antialias: ${if fontconfig.antialias then "1" else "0"}
23 Xft.rgba: ${fontconfig.subpixel.rgba}
24 Xft.lcdfilter: lcd${fontconfig.subpixel.lcdfilter}
25 Xft.hinting: ${if fontconfig.hinting.enable then "1" else "0"}
26 Xft.autohint: ${if fontconfig.hinting.autohint then "1" else "0"}
27 Xft.hintstyle: hintslight
28 '';
29
30 mkCases = session:
31 concatStrings (
32 mapAttrsToList (name: starts: ''
33 (${name})
34 ${concatMapStringsSep "\n " (n: n.start) starts}
35 ;;
36 '') (lib.groupBy (n: n.name) session)
37 );
38
39 # file provided by services.xserver.displayManager.session.wrapper
40 xsessionWrapper = pkgs.writeScript "xsession-wrapper"
41 ''
42 #! ${pkgs.bash}/bin/bash
43
44 # Shared environment setup for graphical sessions.
45
46 . /etc/profile
47 cd "$HOME"
48
49 ${optionalString cfg.startDbusSession ''
50 if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
51 exec ${pkgs.dbus.dbus-launch} --exit-with-session "$0" "$@"
52 fi
53 ''}
54
55 ${optionalString cfg.displayManager.job.logToJournal ''
56 if [ -z "$_DID_SYSTEMD_CAT" ]; then
57 export _DID_SYSTEMD_CAT=1
58 exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$@"
59 fi
60 ''}
61
62 ${optionalString cfg.displayManager.job.logToFile ''
63 exec &> >(tee ~/.xsession-errors)
64 ''}
65
66 # Start PulseAudio if enabled.
67 ${optionalString (config.hardware.pulseaudio.enable) ''
68 # Publish access credentials in the root window.
69 if ${config.hardware.pulseaudio.package.out}/bin/pulseaudio --dump-modules | grep module-x11-publish &> /dev/null; then
70 ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY"
71 fi
72 ''}
73
74 # Tell systemd about our $DISPLAY and $XAUTHORITY.
75 # This is needed by the ssh-agent unit.
76 #
77 # Also tell systemd about the dbus session bus address.
78 # This is required by user units using the session bus.
79 ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS
80
81 # Load X defaults.
82 # FIXME: Check XDG_SESSION_TYPE against x11
83 ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
84 if test -e ~/.Xresources; then
85 ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
86 elif test -e ~/.Xdefaults; then
87 ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults
88 fi
89
90 # Speed up application start by 50-150ms according to
91 # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
92 rm -rf "$HOME/.compose-cache"
93 mkdir "$HOME/.compose-cache"
94
95 # Work around KDE errors when a user first logs in and
96 # .local/share doesn't exist yet.
97 mkdir -p "$HOME/.local/share"
98
99 unset _DID_SYSTEMD_CAT
100
101 ${cfg.displayManager.sessionCommands}
102
103 # Allow the user to execute commands at the beginning of the X session.
104 if test -f ~/.xprofile; then
105 source ~/.xprofile
106 fi
107
108 # Start systemd user services for graphical sessions
109 ${config.systemd.package}/bin/systemctl --user start graphical-session.target
110
111 # Allow the user to setup a custom session type.
112 if test -x ~/.xsession; then
113 exec ~/.xsession
114 fi
115
116 if test "$1"; then
117 # Run the supplied session command. Remove any double quotes with eval.
118 eval exec "$@"
119 else
120 # Fall back to the default window/desktopManager
121 exec ${cfg.displayManager.session.script}
122 fi
123 '';
124
125 # file provided by services.xserver.displayManager.session.script
126 xsession = wm: dm: pkgs.writeScript "xsession"
127 ''
128 #! ${pkgs.bash}/bin/bash
129
130 # Legacy session script used to construct .desktop files from
131 # `services.xserver.displayManager.session` entries. Called from
132 # `sessionWrapper`.
133
134 # Expected parameters:
135 # $1 = <desktop-manager>+<window-manager>
136
137 # The first argument of this script is the session type.
138 sessionType="$1"
139 if [ "$sessionType" = default ]; then sessionType=""; fi
140
141 # The session type is "<desktop-manager>+<window-manager>", so
142 # extract those (see:
143 # http://wiki.bash-hackers.org/syntax/pe#substring_removal).
144 windowManager="''${sessionType##*+}"
145 : ''${windowManager:=${cfg.windowManager.default}}
146 desktopManager="''${sessionType%%+*}"
147 : ''${desktopManager:=${cfg.desktopManager.default}}
148
149 # Start the window manager.
150 case "$windowManager" in
151 ${mkCases wm}
152 (*) echo "$0: Window manager '$windowManager' not found.";;
153 esac
154
155 # Start the desktop manager.
156 case "$desktopManager" in
157 ${mkCases dm}
158 (*) echo "$0: Desktop manager '$desktopManager' not found.";;
159 esac
160
161 ${optionalString cfg.updateDbusEnvironment ''
162 ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
163 ''}
164
165 test -n "$waitPID" && wait "$waitPID"
166
167 ${config.systemd.package}/bin/systemctl --user stop graphical-session.target
168
169 exit 0
170 '';
171
172 # Desktop Entry Specification:
173 # - https://standards.freedesktop.org/desktop-entry-spec/latest/
174 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
175 mkDesktops = names: pkgs.runCommand "desktops"
176 { # trivial derivation
177 preferLocalBuild = true;
178 allowSubstitutes = false;
179 }
180 ''
181 mkdir -p "$out/share/xsessions"
182 ${concatMapStrings (n: ''
183 cat - > "$out/share/xsessions/${n}.desktop" << EODESKTOP
184 [Desktop Entry]
185 Version=1.0
186 Type=XSession
187 TryExec=${cfg.displayManager.session.script}
188 Exec=${cfg.displayManager.session.script} "${n}"
189 Name=${n}
190 Comment=
191 EODESKTOP
192 '') names}
193
194 ${concatMapStrings (pkg: ''
195 ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
196 '') cfg.displayManager.extraSessionFilePackages}
197 '';
198
199in
200
201{
202
203 options = {
204
205 services.xserver.displayManager = {
206
207 xauthBin = mkOption {
208 internal = true;
209 default = "${xorg.xauth}/bin/xauth";
210 description = "Path to the <command>xauth</command> program used by display managers.";
211 };
212
213 xserverBin = mkOption {
214 type = types.path;
215 description = "Path to the X server used by display managers.";
216 };
217
218 xserverArgs = mkOption {
219 type = types.listOf types.str;
220 default = [];
221 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
222 description = "List of arguments for the X server.";
223 };
224
225 sessionCommands = mkOption {
226 type = types.lines;
227 default = "";
228 example =
229 ''
230 xmessage "Hello World!" &
231 '';
232 description = "Shell commands executed just before the window or desktop manager is started.";
233 };
234
235 hiddenUsers = mkOption {
236 type = types.listOf types.str;
237 default = [ "nobody" ];
238 description = ''
239 A list of users which will not be shown in the display manager.
240 '';
241 };
242
243 extraSessionFilePackages = mkOption {
244 type = types.listOf types.package;
245 default = [];
246 description = ''
247 A list of packages containing xsession files to be passed to the display manager.
248 '';
249 };
250
251 session = mkOption {
252 default = [];
253 example = literalExample
254 ''
255 [ { manage = "desktop";
256 name = "xterm";
257 start = '''
258 ''${pkgs.xterm}/bin/xterm -ls &
259 waitPID=$!
260 ''';
261 }
262 ]
263 '';
264 description = ''
265 List of sessions supported with the command used to start each
266 session. Each session script can set the
267 <varname>waitPID</varname> shell variable to make this script
268 wait until the end of the user session. Each script is used
269 to define either a windows manager or a desktop manager. These
270 can be differentiated by setting the attribute
271 <varname>manage</varname> either to <literal>"window"</literal>
272 or <literal>"desktop"</literal>.
273
274 The list of desktop manager and window manager should appear
275 inside the display manager with the desktop manager name
276 followed by the window manager name.
277 '';
278 apply = list: rec {
279 wm = filter (s: s.manage == "window") list;
280 dm = filter (s: s.manage == "desktop") list;
281 names = flip concatMap dm
282 (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name))
283 (filter (w: d.name != "none" || w.name != "none") wm));
284 desktops = mkDesktops names;
285 script = xsession wm dm;
286 wrapper = xsessionWrapper;
287 };
288 };
289
290 job = {
291
292 preStart = mkOption {
293 type = types.lines;
294 default = "";
295 example = "rm -f /var/log/my-display-manager.log";
296 description = "Script executed before the display manager is started.";
297 };
298
299 execCmd = mkOption {
300 type = types.str;
301 example = literalExample ''
302 "''${pkgs.slim}/bin/slim"
303 '';
304 description = "Command to start the display manager.";
305 };
306
307 environment = mkOption {
308 type = types.attrsOf types.unspecified;
309 default = {};
310 example = { SLIM_CFGFILE = "/etc/slim.conf"; };
311 description = "Additional environment variables needed by the display manager.";
312 };
313
314 logToFile = mkOption {
315 type = types.bool;
316 default = false;
317 description = ''
318 Whether the display manager redirects the output of the
319 session script to <filename>~/.xsession-errors</filename>.
320 '';
321 };
322
323 logToJournal = mkOption {
324 type = types.bool;
325 default = true;
326 description = ''
327 Whether the display manager redirects the output of the
328 session script to the systemd journal.
329 '';
330 };
331
332 };
333
334 };
335
336 };
337
338 config = {
339 services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X";
340
341 systemd.user.targets.graphical-session = {
342 unitConfig = {
343 RefuseManualStart = false;
344 StopWhenUnneeded = false;
345 };
346 };
347 };
348
349 imports = [
350 (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ]
351 "The option is no longer necessary because all display managers have already delegated lid management to systemd.")
352 ];
353
354}