1# This module declares the options to define a *display manager*, the 2# program responsible for handling X logins (such as xdm, 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: hintslight 28 ''; 29 30 mkCases = session: 31 concatStrings ( 32 mapAttrsToList (name: starts: '' 33 (${name}) 34 ${concatMapStringsSep "\n " (n: n.start) starts} 35 ;; 36 '') (lib.groupBy (n: n.name) session) 37 ); 38 39 # file provided by services.xserver.displayManager.session.wrapper 40 xsessionWrapper = pkgs.writeScript "xsession-wrapper" 41 '' 42 #! ${pkgs.bash}/bin/bash 43 44 # Shared environment setup for graphical sessions. 45 46 . /etc/profile 47 cd "$HOME" 48 49 ${optionalString cfg.startDbusSession '' 50 if test -z "$DBUS_SESSION_BUS_ADDRESS"; then 51 exec ${pkgs.dbus.dbus-launch} --exit-with-session "$0" "$@" 52 fi 53 ''} 54 55 ${optionalString cfg.displayManager.job.logToJournal '' 56 if [ -z "$_DID_SYSTEMD_CAT" ]; then 57 export _DID_SYSTEMD_CAT=1 58 exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$@" 59 fi 60 ''} 61 62 ${optionalString cfg.displayManager.job.logToFile '' 63 exec &> >(tee ~/.xsession-errors) 64 ''} 65 66 # Start PulseAudio if enabled. 67 ${optionalString (config.hardware.pulseaudio.enable) '' 68 # Publish access credentials in the root window. 69 if ${config.hardware.pulseaudio.package.out}/bin/pulseaudio --dump-modules | grep module-x11-publish &> /dev/null; then 70 ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY" 71 fi 72 ''} 73 74 # Tell systemd about our $DISPLAY and $XAUTHORITY. 75 # This is needed by the ssh-agent unit. 76 # 77 # Also tell systemd about the dbus session bus address. 78 # This is required by user units using the session bus. 79 ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS 80 81 # Load X defaults. 82 # FIXME: Check XDG_SESSION_TYPE against x11 83 ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft} 84 if test -e ~/.Xresources; then 85 ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources 86 elif test -e ~/.Xdefaults; then 87 ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults 88 fi 89 90 # Speed up application start by 50-150ms according to 91 # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html 92 rm -rf "$HOME/.compose-cache" 93 mkdir "$HOME/.compose-cache" 94 95 # Work around KDE errors when a user first logs in and 96 # .local/share doesn't exist yet. 97 mkdir -p "$HOME/.local/share" 98 99 unset _DID_SYSTEMD_CAT 100 101 ${cfg.displayManager.sessionCommands} 102 103 # Allow the user to execute commands at the beginning of the X session. 104 if test -f ~/.xprofile; then 105 source ~/.xprofile 106 fi 107 108 # Start systemd user services for graphical sessions 109 ${config.systemd.package}/bin/systemctl --user start graphical-session.target 110 111 # Allow the user to setup a custom session type. 112 if test -x ~/.xsession; then 113 exec ~/.xsession 114 fi 115 116 if test "$1"; then 117 # Run the supplied session command. Remove any double quotes with eval. 118 eval exec "$@" 119 else 120 # Fall back to the default window/desktopManager 121 exec ${cfg.displayManager.session.script} 122 fi 123 ''; 124 125 # file provided by services.xserver.displayManager.session.script 126 xsession = wm: dm: pkgs.writeScript "xsession" 127 '' 128 #! ${pkgs.bash}/bin/bash 129 130 # Legacy session script used to construct .desktop files from 131 # `services.xserver.displayManager.session` entries. Called from 132 # `sessionWrapper`. 133 134 # Expected parameters: 135 # $1 = <desktop-manager>+<window-manager> 136 137 # The first argument of this script is the session type. 138 sessionType="$1" 139 if [ "$sessionType" = default ]; then sessionType=""; fi 140 141 # The session type is "<desktop-manager>+<window-manager>", so 142 # extract those (see: 143 # http://wiki.bash-hackers.org/syntax/pe#substring_removal). 144 windowManager="''${sessionType##*+}" 145 : ''${windowManager:=${cfg.windowManager.default}} 146 desktopManager="''${sessionType%%+*}" 147 : ''${desktopManager:=${cfg.desktopManager.default}} 148 149 # Start the window manager. 150 case "$windowManager" in 151 ${mkCases wm} 152 (*) echo "$0: Window manager '$windowManager' not found.";; 153 esac 154 155 # Start the desktop manager. 156 case "$desktopManager" in 157 ${mkCases dm} 158 (*) echo "$0: Desktop manager '$desktopManager' not found.";; 159 esac 160 161 ${optionalString cfg.updateDbusEnvironment '' 162 ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all 163 ''} 164 165 test -n "$waitPID" && wait "$waitPID" 166 167 ${config.systemd.package}/bin/systemctl --user stop graphical-session.target 168 169 exit 0 170 ''; 171 172 # Desktop Entry Specification: 173 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ 174 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html 175 mkDesktops = names: pkgs.runCommand "desktops" 176 { # trivial derivation 177 preferLocalBuild = true; 178 allowSubstitutes = false; 179 } 180 '' 181 mkdir -p "$out/share/xsessions" 182 ${concatMapStrings (n: '' 183 cat - > "$out/share/xsessions/${n}.desktop" << EODESKTOP 184 [Desktop Entry] 185 Version=1.0 186 Type=XSession 187 TryExec=${cfg.displayManager.session.script} 188 Exec=${cfg.displayManager.session.script} "${n}" 189 Name=${n} 190 Comment= 191 EODESKTOP 192 '') names} 193 194 ${concatMapStrings (pkg: '' 195 ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions 196 '') cfg.displayManager.extraSessionFilePackages} 197 ''; 198 199in 200 201{ 202 203 options = { 204 205 services.xserver.displayManager = { 206 207 xauthBin = mkOption { 208 internal = true; 209 default = "${xorg.xauth}/bin/xauth"; 210 description = "Path to the <command>xauth</command> program used by display managers."; 211 }; 212 213 xserverBin = mkOption { 214 type = types.path; 215 description = "Path to the X server used by display managers."; 216 }; 217 218 xserverArgs = mkOption { 219 type = types.listOf types.str; 220 default = []; 221 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ]; 222 description = "List of arguments for the X server."; 223 }; 224 225 sessionCommands = mkOption { 226 type = types.lines; 227 default = ""; 228 example = 229 '' 230 xmessage "Hello World!" & 231 ''; 232 description = "Shell commands executed just before the window or desktop manager is started."; 233 }; 234 235 hiddenUsers = mkOption { 236 type = types.listOf types.str; 237 default = [ "nobody" ]; 238 description = '' 239 A list of users which will not be shown in the display manager. 240 ''; 241 }; 242 243 extraSessionFilePackages = mkOption { 244 type = types.listOf types.package; 245 default = []; 246 description = '' 247 A list of packages containing xsession files to be passed to the display manager. 248 ''; 249 }; 250 251 session = mkOption { 252 default = []; 253 example = literalExample 254 '' 255 [ { manage = "desktop"; 256 name = "xterm"; 257 start = ''' 258 ''${pkgs.xterm}/bin/xterm -ls & 259 waitPID=$! 260 '''; 261 } 262 ] 263 ''; 264 description = '' 265 List of sessions supported with the command used to start each 266 session. Each session script can set the 267 <varname>waitPID</varname> shell variable to make this script 268 wait until the end of the user session. Each script is used 269 to define either a windows manager or a desktop manager. These 270 can be differentiated by setting the attribute 271 <varname>manage</varname> either to <literal>"window"</literal> 272 or <literal>"desktop"</literal>. 273 274 The list of desktop manager and window manager should appear 275 inside the display manager with the desktop manager name 276 followed by the window manager name. 277 ''; 278 apply = list: rec { 279 wm = filter (s: s.manage == "window") list; 280 dm = filter (s: s.manage == "desktop") list; 281 names = flip concatMap dm 282 (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name)) 283 (filter (w: d.name != "none" || w.name != "none") wm)); 284 desktops = mkDesktops names; 285 script = xsession wm dm; 286 wrapper = xsessionWrapper; 287 }; 288 }; 289 290 job = { 291 292 preStart = mkOption { 293 type = types.lines; 294 default = ""; 295 example = "rm -f /var/log/my-display-manager.log"; 296 description = "Script executed before the display manager is started."; 297 }; 298 299 execCmd = mkOption { 300 type = types.str; 301 example = literalExample '' 302 "''${pkgs.slim}/bin/slim" 303 ''; 304 description = "Command to start the display manager."; 305 }; 306 307 environment = mkOption { 308 type = types.attrsOf types.unspecified; 309 default = {}; 310 example = { SLIM_CFGFILE = "/etc/slim.conf"; }; 311 description = "Additional environment variables needed by the display manager."; 312 }; 313 314 logToFile = mkOption { 315 type = types.bool; 316 default = false; 317 description = '' 318 Whether the display manager redirects the output of the 319 session script to <filename>~/.xsession-errors</filename>. 320 ''; 321 }; 322 323 logToJournal = mkOption { 324 type = types.bool; 325 default = true; 326 description = '' 327 Whether the display manager redirects the output of the 328 session script to the systemd journal. 329 ''; 330 }; 331 332 }; 333 334 }; 335 336 }; 337 338 config = { 339 services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X"; 340 341 systemd.user.targets.graphical-session = { 342 unitConfig = { 343 RefuseManualStart = false; 344 StopWhenUnneeded = false; 345 }; 346 }; 347 }; 348 349 imports = [ 350 (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ] 351 "The option is no longer necessary because all display managers have already delegated lid management to systemd.") 352 ]; 353 354}