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 # Work around KDE errors when a user first logs in and
118 # .local/share doesn't exist yet.
119 mkdir -p $HOME/.local/share
120
121 ${cfg.displayManager.sessionCommands}
122
123 # Allow the user to execute commands at the beginning of the X session.
124 if test -f ~/.xprofile; then
125 source ~/.xprofile
126 fi
127
128 # Allow the user to setup a custom session type.
129 if test -x ~/.xsession; then
130 exec ~/.xsession
131 else
132 if test "$sessionType" = "custom"; then
133 sessionType="" # fall-thru if there is no ~/.xsession
134 fi
135 fi
136
137 # The session type is "<desktop-manager> + <window-manager>", so
138 # extract those.
139 windowManager="''${sessionType##* + }"
140 : ''${windowManager:=${cfg.windowManager.default}}
141 desktopManager="''${sessionType% + *}"
142 : ''${desktopManager:=${cfg.desktopManager.default}}
143
144 # Start the window manager.
145 case $windowManager in
146 ${concatMapStrings (s: ''
147 (${s.name})
148 ${s.start}
149 ;;
150 '') wm}
151 (*) echo "$0: Window manager '$windowManager' not found.";;
152 esac
153
154 # Start the desktop manager.
155 case $desktopManager in
156 ${concatMapStrings (s: ''
157 (${s.name})
158 ${s.start}
159 ;;
160 '') dm}
161 (*) echo "$0: Desktop manager '$desktopManager' not found.";;
162 esac
163
164 test -n "$waitPID" && wait "$waitPID"
165 exit 0
166 '';
167
168 mkDesktops = names: pkgs.runCommand "desktops"
169 { # trivial derivation
170 preferLocalBuild = true;
171 allowSubstitutes = false;
172 }
173 ''
174 mkdir -p $out
175 ${concatMapStrings (n: ''
176 cat - > "$out/${n}.desktop" << EODESKTOP
177 [Desktop Entry]
178 Version=1.0
179 Type=XSession
180 TryExec=${cfg.displayManager.session.script}
181 Exec=${cfg.displayManager.session.script} '${n}'
182 X-GDM-BypassXsession=true
183 Name=${n}
184 Comment=
185 EODESKTOP
186 '') names}
187 '';
188
189in
190
191{
192
193 options = {
194
195 services.xserver.displayManager = {
196
197 xauthBin = mkOption {
198 internal = true;
199 default = "${xorg.xauth}/bin/xauth";
200 description = "Path to the <command>xauth</command> program used by display managers.";
201 };
202
203 xserverBin = mkOption {
204 type = types.path;
205 description = "Path to the X server used by display managers.";
206 };
207
208 xserverArgs = mkOption {
209 type = types.listOf types.str;
210 default = [];
211 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
212 description = "List of arguments for the X server.";
213 apply = toString;
214 };
215
216 sessionCommands = mkOption {
217 type = types.lines;
218 default = "";
219 example =
220 ''
221 xmessage "Hello World!" &
222 '';
223 description = "Shell commands executed just before the window or desktop manager is started.";
224 };
225
226 hiddenUsers = mkOption {
227 type = types.listOf types.str;
228 default = [ "nobody" ];
229 description = ''
230 A list of users which will not be shown in the display manager.
231 '';
232 };
233
234 desktopManagerHandlesLidAndPower = mkOption {
235 type = types.bool;
236 default = false;
237 description = ''
238 Whether the display manager should prevent systemd from handling
239 lid and power events. This is normally handled by the desktop
240 environment's power manager. Turn this off when using a minimal
241 X11 setup without a full power manager.
242 '';
243 };
244
245 session = mkOption {
246 default = [];
247 example = literalExample
248 ''
249 [ { manage = "desktop";
250 name = "xterm";
251 start = '''
252 ''${pkgs.xterm}/bin/xterm -ls &
253 waitPID=$!
254 ''';
255 }
256 ]
257 '';
258 description = ''
259 List of sessions supported with the command used to start each
260 session. Each session script can set the
261 <varname>waitPID</varname> shell variable to make this script
262 wait until the end of the user session. Each script is used
263 to define either a windows manager or a desktop manager. These
264 can be differentiated by setting the attribute
265 <varname>manage</varname> either to <literal>"window"</literal>
266 or <literal>"desktop"</literal>.
267
268 The list of desktop manager and window manager should appear
269 inside the display manager with the desktop manager name
270 followed by the window manager name.
271 '';
272 apply = list: rec {
273 wm = filter (s: s.manage == "window") list;
274 dm = filter (s: s.manage == "desktop") list;
275 names = flip concatMap dm
276 (d: map (w: d.name + optionalString (w.name != "none") (" + " + w.name))
277 (filter (w: d.name != "none" || w.name != "none") wm));
278 desktops = mkDesktops names;
279 script = xsession wm dm;
280 };
281 };
282
283 job = {
284
285 preStart = mkOption {
286 type = types.lines;
287 default = "";
288 example = "rm -f /var/log/my-display-manager.log";
289 description = "Script executed before the display manager is started.";
290 };
291
292 execCmd = mkOption {
293 type = types.str;
294 example = literalExample ''
295 "''${pkgs.slim}/bin/slim"
296 '';
297 description = "Command to start the display manager.";
298 };
299
300 environment = mkOption {
301 type = types.attrsOf types.unspecified;
302 default = {};
303 example = { SLIM_CFGFILE = "/etc/slim.conf"; };
304 description = "Additional environment variables needed by the display manager.";
305 };
306
307 logsXsession = mkOption {
308 type = types.bool;
309 default = false;
310 description = ''
311 Whether the display manager redirects the
312 output of the session script to
313 <filename>~/.xsession-errors</filename>.
314 '';
315 };
316
317 };
318
319 };
320
321 };
322
323 config = {
324
325 services.xserver.displayManager.xserverBin = "${xorg.xorgserver}/bin/X";
326
327 };
328
329}