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