1# This module declares the options to define a *display manager*, the
2# program responsible for handling X logins (such as xdm, kdm, 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: hint${fontconfig.hinting.style}
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 ${optionalString cfg.displayManager.logToJournal ''
36 if [ -z "$_DID_SYSTEMD_CAT" ]; then
37 _DID_SYSTEMD_CAT=1 exec ${config.systemd.package}/bin/systemd-cat -t xsession -- "$0" "$@"
38 fi
39 ''}
40
41 . /etc/profile
42 cd "$HOME"
43
44 # The first argument of this script is the session type.
45 sessionType="$1"
46 if [ "$sessionType" = default ]; then sessionType=""; fi
47
48 ${optionalString (!cfg.displayManager.job.logsXsession && !cfg.displayManager.logToJournal) ''
49 exec > ~/.xsession-errors 2>&1
50 ''}
51
52 ${optionalString cfg.startDbusSession ''
53 if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
54 exec ${pkgs.dbus.dbus-launch} --exit-with-session "$0" "$sessionType"
55 fi
56 ''}
57
58 # Handle being called by kdm.
59 if test "''${1:0:1}" = /; then eval exec "$1"; fi
60
61 # Start PulseAudio if enabled.
62 ${optionalString (config.hardware.pulseaudio.enable) ''
63 ${optionalString (!config.hardware.pulseaudio.systemWide)
64 "${config.hardware.pulseaudio.package.out}/bin/pulseaudio --start"
65 }
66
67 # Publish access credentials in the root window.
68 ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY"
69 ''}
70
71 # Tell systemd about our $DISPLAY. This is needed by the
72 # ssh-agent unit.
73 ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY
74
75 # Load X defaults.
76 ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
77 if test -e ~/.Xresources; then
78 ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
79 elif test -e ~/.Xdefaults; then
80 ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults
81 fi
82
83 # Speed up application start by 50-150ms according to
84 # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
85 rm -rf $HOME/.compose-cache
86 mkdir $HOME/.compose-cache
87
88 # Work around KDE errors when a user first logs in and
89 # .local/share doesn't exist yet.
90 mkdir -p $HOME/.local/share
91
92 unset _DID_SYSTEMD_CAT
93
94 ${cfg.displayManager.sessionCommands}
95
96 # Allow the user to execute commands at the beginning of the X session.
97 if test -f ~/.xprofile; then
98 source ~/.xprofile
99 fi
100
101 # Allow the user to setup a custom session type.
102 if test -x ~/.xsession; then
103 exec ~/.xsession
104 else
105 if test "$sessionType" = "custom"; then
106 sessionType="" # fall-thru if there is no ~/.xsession
107 fi
108 fi
109
110 # The session type is "<desktop-manager> + <window-manager>", so
111 # extract those.
112 windowManager="''${sessionType##* + }"
113 : ''${windowManager:=${cfg.windowManager.default}}
114 desktopManager="''${sessionType% + *}"
115 : ''${desktopManager:=${cfg.desktopManager.default}}
116
117 # Start the window manager.
118 case $windowManager in
119 ${concatMapStrings (s: ''
120 (${s.name})
121 ${s.start}
122 ;;
123 '') wm}
124 (*) echo "$0: Window manager '$windowManager' not found.";;
125 esac
126
127 # Start the desktop manager.
128 case $desktopManager in
129 ${concatMapStrings (s: ''
130 (${s.name})
131 ${s.start}
132 ;;
133 '') dm}
134 (*) echo "$0: Desktop manager '$desktopManager' not found.";;
135 esac
136
137 # FIXME: gdbus should not be in glib.dev!
138 ${optionalString (cfg.startDbusSession && cfg.updateDbusEnvironment) ''
139 ${pkgs.glib.dev}/bin/gdbus call --session \
140 --dest org.freedesktop.DBus --object-path /org/freedesktop/DBus \
141 --method org.freedesktop.DBus.UpdateActivationEnvironment \
142 "{$(env | ${pkgs.gnused}/bin/sed "s/'/\\\\'/g; s/\([^=]*\)=\(.*\)/'\1':'\2'/" \
143 | ${pkgs.coreutils}/bin/paste -sd,)}"
144 ''}
145
146 test -n "$waitPID" && wait "$waitPID"
147 exit 0
148 '';
149
150 mkDesktops = names: pkgs.runCommand "desktops"
151 { # trivial derivation
152 preferLocalBuild = true;
153 allowSubstitutes = false;
154 }
155 ''
156 mkdir -p $out
157 ${concatMapStrings (n: ''
158 cat - > "$out/${n}.desktop" << EODESKTOP
159 [Desktop Entry]
160 Version=1.0
161 Type=XSession
162 TryExec=${cfg.displayManager.session.script}
163 Exec=${cfg.displayManager.session.script} '${n}'
164 X-GDM-BypassXsession=true
165 Name=${n}
166 Comment=
167 EODESKTOP
168 '') names}
169 '';
170
171in
172
173{
174
175 options = {
176
177 services.xserver.displayManager = {
178
179 xauthBin = mkOption {
180 internal = true;
181 default = "${xorg.xauth}/bin/xauth";
182 description = "Path to the <command>xauth</command> program used by display managers.";
183 };
184
185 xserverBin = mkOption {
186 type = types.path;
187 description = "Path to the X server used by display managers.";
188 };
189
190 xserverArgs = mkOption {
191 type = types.listOf types.str;
192 default = [];
193 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
194 description = "List of arguments for the X server.";
195 apply = toString;
196 };
197
198 sessionCommands = mkOption {
199 type = types.lines;
200 default = "";
201 example =
202 ''
203 xmessage "Hello World!" &
204 '';
205 description = "Shell commands executed just before the window or desktop manager is started.";
206 };
207
208 hiddenUsers = mkOption {
209 type = types.listOf types.str;
210 default = [ "nobody" ];
211 description = ''
212 A list of users which will not be shown in the display manager.
213 '';
214 };
215
216 session = mkOption {
217 default = [];
218 example = literalExample
219 ''
220 [ { manage = "desktop";
221 name = "xterm";
222 start = '''
223 ''${pkgs.xterm}/bin/xterm -ls &
224 waitPID=$!
225 ''';
226 }
227 ]
228 '';
229 description = ''
230 List of sessions supported with the command used to start each
231 session. Each session script can set the
232 <varname>waitPID</varname> shell variable to make this script
233 wait until the end of the user session. Each script is used
234 to define either a windows manager or a desktop manager. These
235 can be differentiated by setting the attribute
236 <varname>manage</varname> either to <literal>"window"</literal>
237 or <literal>"desktop"</literal>.
238
239 The list of desktop manager and window manager should appear
240 inside the display manager with the desktop manager name
241 followed by the window manager name.
242 '';
243 apply = list: rec {
244 wm = filter (s: s.manage == "window") list;
245 dm = filter (s: s.manage == "desktop") list;
246 names = flip concatMap dm
247 (d: map (w: d.name + optionalString (w.name != "none") (" + " + w.name))
248 (filter (w: d.name != "none" || w.name != "none") wm));
249 desktops = mkDesktops names;
250 script = xsession wm dm;
251 };
252 };
253
254 job = {
255
256 preStart = mkOption {
257 type = types.lines;
258 default = "";
259 example = "rm -f /var/log/my-display-manager.log";
260 description = "Script executed before the display manager is started.";
261 };
262
263 execCmd = mkOption {
264 type = types.str;
265 example = literalExample ''
266 "''${pkgs.slim}/bin/slim"
267 '';
268 description = "Command to start the display manager.";
269 };
270
271 environment = mkOption {
272 type = types.attrsOf types.unspecified;
273 default = {};
274 example = { SLIM_CFGFILE = "/etc/slim.conf"; };
275 description = "Additional environment variables needed by the display manager.";
276 };
277
278 logsXsession = mkOption {
279 type = types.bool;
280 default = false;
281 description = ''
282 Whether the display manager redirects the
283 output of the session script to
284 <filename>~/.xsession-errors</filename>.
285 '';
286 };
287
288 };
289
290 logToJournal = mkOption {
291 type = types.bool;
292 default = true;
293 description = ''
294 By default, the stdout/stderr of sessions is written
295 to <filename>~/.xsession-errors</filename>. When this option
296 is enabled, it will instead be written to the journal.
297 '';
298 };
299
300 };
301
302 };
303
304 config = {
305 services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X";
306 };
307
308 imports = [
309 (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ]
310 "The option is no longer necessary because all display managers have already delegated lid management to systemd.")
311 ];
312
313}