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 vaapiDrivers = pkgs.buildEnv {
20 name = "vaapi-drivers";
21 paths = cfg.vaapiDrivers;
22 # We only want /lib/dri, but with a single input path, we need "/" for it to work
23 pathsToLink = [ "/" ];
24 };
25
26 fontconfig = config.fonts.fontconfig;
27 xresourcesXft = pkgs.writeText "Xresources-Xft" ''
28 ${optionalString (fontconfig.dpi != 0) ''Xft.dpi: ${toString fontconfig.dpi}''}
29 Xft.antialias: ${if fontconfig.antialias then "1" else "0"}
30 Xft.rgba: ${fontconfig.subpixel.rgba}
31 Xft.lcdfilter: lcd${fontconfig.subpixel.lcdfilter}
32 Xft.hinting: ${if fontconfig.hinting.enable then "1" else "0"}
33 Xft.autohint: ${if fontconfig.hinting.autohint then "1" else "0"}
34 Xft.hintstyle: hint${fontconfig.hinting.style}
35 '';
36
37 # file provided by services.xserver.displayManager.session.script
38 xsession = wm: dm: pkgs.writeScript "xsession"
39 ''
40 #! /bin/sh
41
42 . /etc/profile
43 cd "$HOME"
44
45 # The first argument of this script is the session type.
46 sessionType="$1"
47 if [ "$sessionType" = default ]; then sessionType=""; fi
48
49 ${optionalString (!cfg.displayManager.job.logsXsession) ''
50 exec > ~/.xsession-errors 2>&1
51 ''}
52
53 ${optionalString cfg.startDbusSession ''
54 if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
55 exec ${pkgs.dbus.tools}/bin/dbus-launch --exit-with-session "$0" "$sessionType"
56 fi
57 ''}
58
59 ${optionalString cfg.displayManager.desktopManagerHandlesLidAndPower ''
60 # Stop systemd from handling the power button and lid switch,
61 # since presumably the desktop environment will handle these.
62 if [ -z "$_INHIBITION_LOCK_TAKEN" ]; then
63 export _INHIBITION_LOCK_TAKEN=1
64 if ! ${config.systemd.package}/bin/loginctl show-session $XDG_SESSION_ID | grep -q '^RemoteHost='; then
65 exec ${config.systemd.package}/bin/systemd-inhibit --what=handle-lid-switch:handle-power-key --why="Desktop environment handles power events" "$0" "$sessionType"
66 fi
67 fi
68
69 ''}
70
71 ${optionalString cfg.startGnuPGAgent ''
72 if test -z "$SSH_AUTH_SOCK"; then
73 # Restart this script as a child of the GnuPG agent.
74 exec "${pkgs.gnupg}/bin/gpg-agent" \
75 --enable-ssh-support --daemon \
76 --pinentry-program "${pkgs.pinentry}/bin/pinentry-gtk-2" \
77 --write-env-file "$HOME/.gpg-agent-info" \
78 "$0" "$sessionType"
79 fi
80 ''}
81
82 # Handle being called by kdm.
83 if test "''${1:0:1}" = /; then eval exec "$1"; fi
84
85 # Start PulseAudio if enabled.
86 ${optionalString (config.hardware.pulseaudio.enable) ''
87 ${optionalString (!config.hardware.pulseaudio.systemWide)
88 "${config.hardware.pulseaudio.package}/bin/pulseaudio --start"
89 }
90
91 # Publish access credentials in the root window.
92 ${config.hardware.pulseaudio.package}/bin/pactl load-module module-x11-publish "display=$DISPLAY"
93
94 # Keep track of devices. Mostly useful for Phonon/KDE.
95 ${config.hardware.pulseaudio.package}/bin/pactl load-module module-device-manager "do_routing=1"
96 ''}
97
98 # Tell systemd about our $DISPLAY. This is needed by the
99 # ssh-agent unit.
100 ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY
101
102 # Load X defaults.
103 ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
104 if test -e ~/.Xresources; then
105 ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
106 elif test -e ~/.Xdefaults; then
107 ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults
108 fi
109
110 export LIBVA_DRIVERS_PATH=${vaapiDrivers}/lib/dri
111
112 # Speed up application start by 50-150ms according to
113 # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
114 rm -rf $HOME/.compose-cache
115 mkdir $HOME/.compose-cache
116
117 ${cfg.displayManager.sessionCommands}
118
119 # Allow the user to execute commands at the beginning of the X session.
120 if test -f ~/.xprofile; then
121 source ~/.xprofile
122 fi
123
124 # Allow the user to setup a custom session type.
125 if test -x ~/.xsession; then
126 exec ~/.xsession
127 else
128 if test "$sessionType" = "custom"; then
129 sessionType="" # fall-thru if there is no ~/.xsession
130 fi
131 fi
132
133 # The session type is "<desktop-manager> + <window-manager>", so
134 # extract those.
135 windowManager="''${sessionType##* + }"
136 : ''${windowManager:=${cfg.windowManager.default}}
137 desktopManager="''${sessionType% + *}"
138 : ''${desktopManager:=${cfg.desktopManager.default}}
139
140 # Start the window manager.
141 case $windowManager in
142 ${concatMapStrings (s: ''
143 (${s.name})
144 ${s.start}
145 ;;
146 '') wm}
147 (*) echo "$0: Window manager '$windowManager' not found.";;
148 esac
149
150 # Start the desktop manager.
151 case $desktopManager in
152 ${concatMapStrings (s: ''
153 (${s.name})
154 ${s.start}
155 ;;
156 '') dm}
157 (*) echo "$0: Desktop manager '$desktopManager' not found.";;
158 esac
159
160 test -n "$waitPID" && wait "$waitPID"
161 exit 0
162 '';
163
164 mkDesktops = names: pkgs.runCommand "desktops"
165 { # trivial derivation
166 preferLocalBuild = true;
167 allowSubstitutes = false;
168 }
169 ''
170 mkdir -p $out
171 ${concatMapStrings (n: ''
172 cat - > "$out/${n}.desktop" << EODESKTOP
173 [Desktop Entry]
174 Version=1.0
175 Type=XSession
176 TryExec=${cfg.displayManager.session.script}
177 Exec=${cfg.displayManager.session.script} '${n}'
178 X-GDM-BypassXsession=true
179 Name=${n}
180 Comment=
181 EODESKTOP
182 '') names}
183 '';
184
185in
186
187{
188
189 options = {
190
191 services.xserver.displayManager = {
192
193 xauthBin = mkOption {
194 internal = true;
195 default = "${xorg.xauth}/bin/xauth";
196 description = "Path to the <command>xauth</command> program used by display managers.";
197 };
198
199 xserverBin = mkOption {
200 type = types.path;
201 description = "Path to the X server used by display managers.";
202 };
203
204 xserverArgs = mkOption {
205 type = types.listOf types.str;
206 default = [];
207 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
208 description = "List of arguments for the X server.";
209 apply = toString;
210 };
211
212 sessionCommands = mkOption {
213 type = types.lines;
214 default = "";
215 example =
216 ''
217 xmessage "Hello World!" &
218 '';
219 description = "Shell commands executed just before the window or desktop manager is started.";
220 };
221
222 hiddenUsers = mkOption {
223 type = types.listOf types.str;
224 default = [ "nobody" ];
225 description = ''
226 A list of users which will not be shown in the display manager.
227 '';
228 };
229
230 desktopManagerHandlesLidAndPower = mkOption {
231 type = types.bool;
232 default = false;
233 description = ''
234 Whether the display manager should prevent systemd from handling
235 lid and power events. This is normally handled by the desktop
236 environment's power manager. Turn this off when using a minimal
237 X11 setup without a full power manager.
238 '';
239 };
240
241 session = mkOption {
242 default = [];
243 example = literalExample
244 ''
245 [ { manage = "desktop";
246 name = "xterm";
247 start = '''
248 ''${pkgs.xterm}/bin/xterm -ls &
249 waitPID=$!
250 ''';
251 }
252 ]
253 '';
254 description = ''
255 List of sessions supported with the command used to start each
256 session. Each session script can set the
257 <varname>waitPID</varname> shell variable to make this script
258 wait until the end of the user session. Each script is used
259 to define either a windows manager or a desktop manager. These
260 can be differentiated by setting the attribute
261 <varname>manage</varname> either to <literal>"window"</literal>
262 or <literal>"desktop"</literal>.
263
264 The list of desktop manager and window manager should appear
265 inside the display manager with the desktop manager name
266 followed by the window manager name.
267 '';
268 apply = list: rec {
269 wm = filter (s: s.manage == "window") list;
270 dm = filter (s: s.manage == "desktop") list;
271 names = flip concatMap dm
272 (d: map (w: d.name + optionalString (w.name != "none") (" + " + w.name))
273 (filter (w: d.name != "none" || w.name != "none") wm));
274 desktops = mkDesktops names;
275 script = xsession wm dm;
276 };
277 };
278
279 job = {
280
281 preStart = mkOption {
282 type = types.lines;
283 default = "";
284 example = "rm -f /var/log/my-display-manager.log";
285 description = "Script executed before the display manager is started.";
286 };
287
288 execCmd = mkOption {
289 type = types.str;
290 example = literalExample ''
291 "''${pkgs.slim}/bin/slim"
292 '';
293 description = "Command to start the display manager.";
294 };
295
296 environment = mkOption {
297 type = types.attrsOf types.unspecified;
298 default = {};
299 example = { SLIM_CFGFILE = "/etc/slim.conf"; };
300 description = "Additional environment variables needed by the display manager.";
301 };
302
303 logsXsession = mkOption {
304 type = types.bool;
305 default = false;
306 description = ''
307 Whether the display manager redirects the
308 output of the session script to
309 <filename>~/.xsession-errors</filename>.
310 '';
311 };
312
313 };
314
315 };
316
317 };
318
319 config = {
320
321 services.xserver.displayManager.xserverBin = "${xorg.xorgserver}/bin/X";
322
323 };
324
325}