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="See NixOS configuration option 'services.xserver.displayManager.desktopManagerHandlesLidAndPower' for more information." "$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 ''
166 mkdir -p $out
167 ${concatMapStrings (n: ''
168 cat - > "$out/${n}.desktop" << EODESKTOP
169 [Desktop Entry]
170 Version=1.0
171 Type=XSession
172 TryExec=${cfg.displayManager.session.script}
173 Exec=${cfg.displayManager.session.script} '${n}'
174 X-GDM-BypassXsession=true
175 Name=${n}
176 Comment=
177 EODESKTOP
178 '') names}
179 '';
180
181in
182
183{
184
185 options = {
186
187 services.xserver.displayManager = {
188
189 xauthBin = mkOption {
190 internal = true;
191 default = "${xorg.xauth}/bin/xauth";
192 description = "Path to the <command>xauth</command> program used by display managers.";
193 };
194
195 xserverBin = mkOption {
196 type = types.path;
197 description = "Path to the X server used by display managers.";
198 };
199
200 xserverArgs = mkOption {
201 type = types.listOf types.str;
202 default = [];
203 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
204 description = "List of arguments for the X server.";
205 apply = toString;
206 };
207
208 sessionCommands = mkOption {
209 type = types.lines;
210 default = "";
211 example =
212 ''
213 xmessage "Hello World!" &
214 '';
215 description = "Shell commands executed just before the window or desktop manager is started.";
216 };
217
218 hiddenUsers = mkOption {
219 type = types.listOf types.str;
220 default = [ "nobody" ];
221 description = ''
222 A list of users which will not be shown in the display manager.
223 '';
224 };
225
226 desktopManagerHandlesLidAndPower = mkOption {
227 type = types.bool;
228 default = true;
229 description = ''
230 Whether the display manager should prevent systemd from handling
231 lid and power events. This is normally handled by the desktop
232 environment's power manager. Turn this off when using a minimal
233 X11 setup without a full power manager.
234 '';
235 };
236
237 session = mkOption {
238 default = [];
239 example = literalExample
240 ''
241 [ { manage = "desktop";
242 name = "xterm";
243 start = '''
244 ''${pkgs.xterm}/bin/xterm -ls &
245 waitPID=$!
246 ''';
247 }
248 ]
249 '';
250 description = ''
251 List of sessions supported with the command used to start each
252 session. Each session script can set the
253 <varname>waitPID</varname> shell variable to make this script
254 wait until the end of the user session. Each script is used
255 to define either a windows manager or a desktop manager. These
256 can be differentiated by setting the attribute
257 <varname>manage</varname> either to <literal>"window"</literal>
258 or <literal>"desktop"</literal>.
259
260 The list of desktop manager and window manager should appear
261 inside the display manager with the desktop manager name
262 followed by the window manager name.
263 '';
264 apply = list: rec {
265 wm = filter (s: s.manage == "window") list;
266 dm = filter (s: s.manage == "desktop") list;
267 names = flip concatMap dm
268 (d: map (w: d.name + optionalString (w.name != "none") (" + " + w.name))
269 (filter (w: d.name != "none" || w.name != "none") wm));
270 desktops = mkDesktops names;
271 script = xsession wm dm;
272 };
273 };
274
275 job = {
276
277 preStart = mkOption {
278 type = types.lines;
279 default = "";
280 example = "rm -f /var/log/my-display-manager.log";
281 description = "Script executed before the display manager is started.";
282 };
283
284 execCmd = mkOption {
285 type = types.str;
286 example = literalExample ''
287 "''${pkgs.slim}/bin/slim"
288 '';
289 description = "Command to start the display manager.";
290 };
291
292 environment = mkOption {
293 type = types.attrsOf types.unspecified;
294 default = {};
295 example = { SLIM_CFGFILE = "/etc/slim.conf"; };
296 description = "Additional environment variables needed by the display manager.";
297 };
298
299 logsXsession = mkOption {
300 type = types.bool;
301 default = false;
302 description = ''
303 Whether the display manager redirects the
304 output of the session script to
305 <filename>~/.xsession-errors</filename>.
306 '';
307 };
308
309 };
310
311 };
312
313 };
314
315 config = {
316
317 services.xserver.displayManager.xserverBin = "${xorg.xorgserver}/bin/X";
318
319 };
320
321}