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