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