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 # file provided by services.xserver.displayManager.session.script
31 xsession = wm: dm: pkgs.writeScript "xsession"
32 ''
33 #! ${pkgs.bash}/bin/bash
34
35 # Expected parameters:
36 # $1 = <desktop-manager>+<window-manager>
37
38 # Actual parameters (FIXME):
39 # SDDM is calling this script like the following:
40 # $1 = /nix/store/xxx-xsession (= $0)
41 # $2 = <desktop-manager>+<window-manager>
42 # SLiM is using the following parameter:
43 # $1 = /nix/store/xxx-xsession <desktop-manager>+<window-manager>
44 # LightDM keeps the double quotes:
45 # $1 = /nix/store/xxx-xsession "<desktop-manager>+<window-manager>"
46 # The fake/auto display manager doesn't use any parameters and GDM is
47 # broken.
48 # If you want to "debug" this script don't print the parameters to stdout
49 # or stderr because this script will be executed multiple times and the
50 # output won't be visible in the log when the script is executed for the
51 # first time (e.g. append them to a file instead)!
52
53 # All of the above cases are handled by the following hack (FIXME).
54 # Since this line is *very important* for *all display managers* it is
55 # very important to test changes to the following line with all display
56 # managers:
57 if [ "''${1:0:1}" = "/" ]; then eval exec "$1" "$2" ; fi
58
59 # Now it should be safe to assume that the script was called with the
60 # expected parameters.
61
62 . /etc/profile
63 cd "$HOME"
64
65 # The first argument of this script is the session type.
66 sessionType="$1"
67 if [ "$sessionType" = default ]; then sessionType=""; fi
68
69 ${optionalString cfg.startDbusSession ''
70 if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
71 exec ${pkgs.dbus.dbus-launch} --exit-with-session "$0" "$sessionType"
72 fi
73 ''}
74
75 ${optionalString cfg.displayManager.job.logToJournal ''
76 if [ -z "$_DID_SYSTEMD_CAT" ]; then
77 export _DID_SYSTEMD_CAT=1
78 exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$sessionType"
79 fi
80 ''}
81
82 ${optionalString cfg.displayManager.job.logToFile ''
83 exec &> >(tee ~/.xsession-errors)
84 ''}
85
86 # Start PulseAudio if enabled.
87 ${optionalString (config.hardware.pulseaudio.enable) ''
88 ${optionalString (!config.hardware.pulseaudio.systemWide)
89 "${config.hardware.pulseaudio.package.out}/bin/pulseaudio --start"
90 }
91
92 # Publish access credentials in the root window.
93 ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY"
94 ''}
95
96 # Tell systemd about our $DISPLAY and $XAUTHORITY.
97 # This is needed by the ssh-agent unit.
98 #
99 # Also tell systemd about the dbus session bus address.
100 # This is required by user units using the session bus.
101 ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS
102
103 # Load X defaults.
104 ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
105 if test -e ~/.Xresources; then
106 ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
107 elif test -e ~/.Xdefaults; then
108 ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults
109 fi
110
111 # Speed up application start by 50-150ms according to
112 # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
113 rm -rf "$HOME/.compose-cache"
114 mkdir "$HOME/.compose-cache"
115
116 # Work around KDE errors when a user first logs in and
117 # .local/share doesn't exist yet.
118 mkdir -p "$HOME/.local/share"
119
120 unset _DID_SYSTEMD_CAT
121
122 ${cfg.displayManager.sessionCommands}
123
124 # Allow the user to execute commands at the beginning of the X session.
125 if test -f ~/.xprofile; then
126 source ~/.xprofile
127 fi
128
129 # Start systemd user services for graphical sessions
130 ${config.systemd.package}/bin/systemctl --user start graphical-session.target
131
132 # Allow the user to setup a custom session type.
133 if test -x ~/.xsession; then
134 exec ~/.xsession
135 else
136 if test "$sessionType" = "custom"; then
137 sessionType="" # fall-thru if there is no ~/.xsession
138 fi
139 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 ${concatMapStrings (s: ''
152 (${s.name})
153 ${s.start}
154 ;;
155 '') wm}
156 (*) echo "$0: Window manager '$windowManager' not found.";;
157 esac
158
159 # Start the desktop manager.
160 case "$desktopManager" in
161 ${concatMapStrings (s: ''
162 (${s.name})
163 ${s.start}
164 ;;
165 '') dm}
166 (*) echo "$0: Desktop manager '$desktopManager' not found.";;
167 esac
168
169 ${optionalString cfg.updateDbusEnvironment ''
170 ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
171 ''}
172
173 test -n "$waitPID" && wait "$waitPID"
174
175 ${config.systemd.package}/bin/systemctl --user stop graphical-session.target
176
177 exit 0
178 '';
179
180 # Desktop Entry Specification:
181 # - https://standards.freedesktop.org/desktop-entry-spec/latest/
182 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
183 mkDesktops = names: pkgs.runCommand "desktops"
184 { # trivial derivation
185 preferLocalBuild = true;
186 allowSubstitutes = false;
187 }
188 ''
189 mkdir -p "$out"
190 ${concatMapStrings (n: ''
191 cat - > "$out/${n}.desktop" << EODESKTOP
192 [Desktop Entry]
193 Version=1.0
194 Type=XSession
195 TryExec=${cfg.displayManager.session.script}
196 Exec=${cfg.displayManager.session.script} "${n}"
197 X-GDM-BypassXsession=true
198 Name=${n}
199 Comment=
200 EODESKTOP
201 '') names}
202 '';
203
204in
205
206{
207
208 options = {
209
210 services.xserver.displayManager = {
211
212 xauthBin = mkOption {
213 internal = true;
214 default = "${xorg.xauth}/bin/xauth";
215 description = "Path to the <command>xauth</command> program used by display managers.";
216 };
217
218 xserverBin = mkOption {
219 type = types.path;
220 description = "Path to the X server used by display managers.";
221 };
222
223 xserverArgs = mkOption {
224 type = types.listOf types.str;
225 default = [];
226 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
227 description = "List of arguments for the X server.";
228 };
229
230 sessionCommands = mkOption {
231 type = types.lines;
232 default = "";
233 example =
234 ''
235 xmessage "Hello World!" &
236 '';
237 description = "Shell commands executed just before the window or desktop manager is started.";
238 };
239
240 hiddenUsers = mkOption {
241 type = types.listOf types.str;
242 default = [ "nobody" ];
243 description = ''
244 A list of users which will not be shown in the display manager.
245 '';
246 };
247
248 session = mkOption {
249 default = [];
250 example = literalExample
251 ''
252 [ { manage = "desktop";
253 name = "xterm";
254 start = '''
255 ''${pkgs.xterm}/bin/xterm -ls &
256 waitPID=$!
257 ''';
258 }
259 ]
260 '';
261 description = ''
262 List of sessions supported with the command used to start each
263 session. Each session script can set the
264 <varname>waitPID</varname> shell variable to make this script
265 wait until the end of the user session. Each script is used
266 to define either a windows manager or a desktop manager. These
267 can be differentiated by setting the attribute
268 <varname>manage</varname> either to <literal>"window"</literal>
269 or <literal>"desktop"</literal>.
270
271 The list of desktop manager and window manager should appear
272 inside the display manager with the desktop manager name
273 followed by the window manager name.
274 '';
275 apply = list: rec {
276 wm = filter (s: s.manage == "window") list;
277 dm = filter (s: s.manage == "desktop") list;
278 names = flip concatMap dm
279 (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name))
280 (filter (w: d.name != "none" || w.name != "none") wm));
281 desktops = mkDesktops names;
282 script = xsession wm dm;
283 };
284 };
285
286 job = {
287
288 preStart = mkOption {
289 type = types.lines;
290 default = "";
291 example = "rm -f /var/log/my-display-manager.log";
292 description = "Script executed before the display manager is started.";
293 };
294
295 execCmd = mkOption {
296 type = types.str;
297 example = literalExample ''
298 "''${pkgs.slim}/bin/slim"
299 '';
300 description = "Command to start the display manager.";
301 };
302
303 environment = mkOption {
304 type = types.attrsOf types.unspecified;
305 default = {};
306 example = { SLIM_CFGFILE = "/etc/slim.conf"; };
307 description = "Additional environment variables needed by the display manager.";
308 };
309
310 logToFile = mkOption {
311 type = types.bool;
312 default = false;
313 description = ''
314 Whether the display manager redirects the output of the
315 session script to <filename>~/.xsession-errors</filename>.
316 '';
317 };
318
319 logToJournal = mkOption {
320 type = types.bool;
321 default = true;
322 description = ''
323 Whether the display manager redirects the output of the
324 session script to the systemd journal.
325 '';
326 };
327
328 };
329
330 };
331
332 };
333
334 config = {
335 services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X";
336
337 systemd.user.targets.graphical-session = {
338 unitConfig = {
339 RefuseManualStart = false;
340 StopWhenUnneeded = false;
341 };
342 };
343 };
344
345 imports = [
346 (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ]
347 "The option is no longer necessary because all display managers have already delegated lid management to systemd.")
348 ];
349
350}