at 21.11-pre 18 kB view raw
1# This module declares the options to define a *display manager*, the 2# program responsible for handling X logins (such as LightDM, GDM, or SDDM). 3# 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.sessionData.wrapper 31 xsessionWrapper = pkgs.writeScript "xsession-wrapper" 32 '' 33 #! ${pkgs.bash}/bin/bash 34 35 # Shared environment setup for graphical sessions. 36 37 . /etc/profile 38 cd "$HOME" 39 40 # Allow the user to execute commands at the beginning of the X session. 41 if test -f ~/.xprofile; then 42 source ~/.xprofile 43 fi 44 45 ${optionalString cfg.displayManager.job.logToJournal '' 46 if [ -z "$_DID_SYSTEMD_CAT" ]; then 47 export _DID_SYSTEMD_CAT=1 48 exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$@" 49 fi 50 ''} 51 52 ${optionalString cfg.displayManager.job.logToFile '' 53 exec &> >(tee ~/.xsession-errors) 54 ''} 55 56 # Load X defaults. This should probably be safe on wayland too. 57 ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft} 58 if test -e ~/.Xresources; then 59 ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources 60 elif test -e ~/.Xdefaults; then 61 ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults 62 fi 63 64 # Import environment variables into the systemd user environment. 65 ${optionalString (cfg.displayManager.importedVariables != []) ( 66 "/run/current-system/systemd/bin/systemctl --user import-environment " 67 + toString (unique cfg.displayManager.importedVariables) 68 )} 69 70 # Speed up application start by 50-150ms according to 71 # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html 72 compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}" 73 mkdir -p "$compose_cache" 74 # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory, 75 # defensively try to delete cache *files* only, following the file format specified in 76 # https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/modules/im/ximcp/imLcIm.c#L353-358 77 # sprintf (*res, "%s/%c%d_%03x_%08x_%08x", dir, _XimGetMyEndian(), XIM_CACHE_VERSION, (unsigned int)sizeof (DefTree), hash, hash2); 78 ${pkgs.findutils}/bin/find "$compose_cache" -maxdepth 1 -regextype posix-extended -regex '.*/[Bl][0-9]+_[0-9a-f]{3}_[0-9a-f]{8}_[0-9a-f]{8}' -delete 79 unset compose_cache 80 81 # Work around KDE errors when a user first logs in and 82 # .local/share doesn't exist yet. 83 mkdir -p "''${XDG_DATA_HOME:-$HOME/.local/share}" 84 85 unset _DID_SYSTEMD_CAT 86 87 ${cfg.displayManager.sessionCommands} 88 89 # Start systemd user services for graphical sessions 90 /run/current-system/systemd/bin/systemctl --user start graphical-session.target 91 92 # Allow the user to setup a custom session type. 93 if test -x ~/.xsession; then 94 eval exec ~/.xsession "$@" 95 fi 96 97 if test "$1"; then 98 # Run the supplied session command. Remove any double quotes with eval. 99 eval exec "$@" 100 else 101 # TODO: Do we need this? Should not the session always exist? 102 echo "error: unknown session $1" 1>&2 103 exit 1 104 fi 105 ''; 106 107 installedSessions = pkgs.runCommand "desktops" 108 { # trivial derivation 109 preferLocalBuild = true; 110 allowSubstitutes = false; 111 } 112 '' 113 mkdir -p "$out/share/"{xsessions,wayland-sessions} 114 115 ${concatMapStrings (pkg: '' 116 for n in ${concatStringsSep " " pkg.providedSessions}; do 117 if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \ 118 -f ${pkg}/share/xsessions/$n.desktop; then 119 echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:" 120 echo " ${pkg}" 121 return 1 122 fi 123 done 124 125 if test -d ${pkg}/share/xsessions; then 126 ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions 127 fi 128 if test -d ${pkg}/share/wayland-sessions; then 129 ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions 130 fi 131 '') cfg.displayManager.sessionPackages} 132 ''; 133 134 dmDefault = cfg.desktopManager.default; 135 # fallback default for cases when only default wm is set 136 dmFallbackDefault = if dmDefault != null then dmDefault else "none"; 137 wmDefault = cfg.windowManager.default; 138 139 defaultSessionFromLegacyOptions = dmFallbackDefault + optionalString (wmDefault != null && wmDefault != "none") "+${wmDefault}"; 140 141in 142 143{ 144 options = { 145 146 services.xserver.displayManager = { 147 148 xauthBin = mkOption { 149 internal = true; 150 default = "${xorg.xauth}/bin/xauth"; 151 description = "Path to the <command>xauth</command> program used by display managers."; 152 }; 153 154 xserverBin = mkOption { 155 type = types.path; 156 description = "Path to the X server used by display managers."; 157 }; 158 159 xserverArgs = mkOption { 160 type = types.listOf types.str; 161 default = []; 162 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ]; 163 description = "List of arguments for the X server."; 164 }; 165 166 setupCommands = mkOption { 167 type = types.lines; 168 default = ""; 169 description = '' 170 Shell commands executed just after the X server has started. 171 172 This option is only effective for display managers for which this feature 173 is supported; currently these are LightDM, GDM and SDDM. 174 ''; 175 }; 176 177 sessionCommands = mkOption { 178 type = types.lines; 179 default = ""; 180 example = 181 '' 182 xmessage "Hello World!" & 183 ''; 184 description = '' 185 Shell commands executed just before the window or desktop manager is 186 started. These commands are not currently sourced for Wayland sessions. 187 ''; 188 }; 189 190 hiddenUsers = mkOption { 191 type = types.listOf types.str; 192 default = [ "nobody" ]; 193 description = '' 194 A list of users which will not be shown in the display manager. 195 ''; 196 }; 197 198 sessionPackages = mkOption { 199 type = with types; listOf (package // { 200 description = "package with provided sessions"; 201 check = p: assertMsg 202 (package.check p && p ? providedSessions 203 && p.providedSessions != [] && all isString p.providedSessions) 204 '' 205 Package, '${p.name}', did not specify any session names, as strings, in 206 'passthru.providedSessions'. This is required when used as a session package. 207 208 The session names can be looked up in: 209 ${p}/share/xsessions 210 ${p}/share/wayland-sessions 211 ''; 212 }); 213 default = []; 214 description = '' 215 A list of packages containing x11 or wayland session files to be passed to the display manager. 216 ''; 217 }; 218 219 session = mkOption { 220 default = []; 221 example = literalExample 222 '' 223 [ { manage = "desktop"; 224 name = "xterm"; 225 start = ''' 226 ''${pkgs.xterm}/bin/xterm -ls & 227 waitPID=$! 228 '''; 229 } 230 ] 231 ''; 232 description = '' 233 List of sessions supported with the command used to start each 234 session. Each session script can set the 235 <varname>waitPID</varname> shell variable to make this script 236 wait until the end of the user session. Each script is used 237 to define either a window manager or a desktop manager. These 238 can be differentiated by setting the attribute 239 <varname>manage</varname> either to <literal>"window"</literal> 240 or <literal>"desktop"</literal>. 241 242 The list of desktop manager and window manager should appear 243 inside the display manager with the desktop manager name 244 followed by the window manager name. 245 ''; 246 }; 247 248 sessionData = mkOption { 249 description = "Data exported for display managers convenience"; 250 internal = true; 251 default = {}; 252 apply = val: { 253 wrapper = xsessionWrapper; 254 desktops = installedSessions; 255 sessionNames = concatMap (p: p.providedSessions) cfg.displayManager.sessionPackages; 256 # We do not want to force users to set defaultSession when they have only single DE. 257 autologinSession = 258 if cfg.displayManager.defaultSession != null then 259 cfg.displayManager.defaultSession 260 else if cfg.displayManager.sessionData.sessionNames != [] then 261 head cfg.displayManager.sessionData.sessionNames 262 else 263 null; 264 }; 265 }; 266 267 defaultSession = mkOption { 268 type = with types; nullOr str // { 269 description = "session name"; 270 check = d: 271 assertMsg (d != null -> (str.check d && elem d cfg.displayManager.sessionData.sessionNames)) '' 272 Default graphical session, '${d}', not found. 273 Valid names for 'services.xserver.displayManager.defaultSession' are: 274 ${concatStringsSep "\n " cfg.displayManager.sessionData.sessionNames} 275 ''; 276 }; 277 default = 278 if dmDefault != null || wmDefault != null then 279 defaultSessionFromLegacyOptions 280 else 281 null; 282 example = "gnome"; 283 description = '' 284 Graphical session to pre-select in the session chooser (only effective for GDM and LightDM). 285 286 On GDM, LightDM and SDDM, it will also be used as a session for auto-login. 287 ''; 288 }; 289 290 importedVariables = mkOption { 291 type = types.listOf (types.strMatching "[a-zA-Z_][a-zA-Z0-9_]*"); 292 visible = false; 293 description = '' 294 Environment variables to import into the systemd user environment. 295 ''; 296 }; 297 298 job = { 299 300 preStart = mkOption { 301 type = types.lines; 302 default = ""; 303 example = "rm -f /var/log/my-display-manager.log"; 304 description = "Script executed before the display manager is started."; 305 }; 306 307 execCmd = mkOption { 308 type = types.str; 309 example = literalExample '' 310 "''${pkgs.lightdm}/bin/lightdm" 311 ''; 312 description = "Command to start the display manager."; 313 }; 314 315 environment = mkOption { 316 type = types.attrsOf types.unspecified; 317 default = {}; 318 description = "Additional environment variables needed by the display manager."; 319 }; 320 321 logToFile = mkOption { 322 type = types.bool; 323 default = false; 324 description = '' 325 Whether the display manager redirects the output of the 326 session script to <filename>~/.xsession-errors</filename>. 327 ''; 328 }; 329 330 logToJournal = mkOption { 331 type = types.bool; 332 default = true; 333 description = '' 334 Whether the display manager redirects the output of the 335 session script to the systemd journal. 336 ''; 337 }; 338 339 }; 340 341 # Configuration for automatic login. Common for all DM. 342 autoLogin = mkOption { 343 type = types.submodule { 344 options = { 345 enable = mkOption { 346 type = types.bool; 347 default = cfg.displayManager.autoLogin.user != null; 348 description = '' 349 Automatically log in as <option>autoLogin.user</option>. 350 ''; 351 }; 352 353 user = mkOption { 354 type = types.nullOr types.str; 355 default = null; 356 description = '' 357 User to be used for the automatic login. 358 ''; 359 }; 360 }; 361 }; 362 363 default = {}; 364 description = '' 365 Auto login configuration attrset. 366 ''; 367 }; 368 369 }; 370 371 }; 372 373 config = { 374 assertions = [ 375 { assertion = cfg.displayManager.autoLogin.enable -> cfg.displayManager.autoLogin.user != null; 376 message = '' 377 services.xserver.displayManager.autoLogin.enable requires services.xserver.displayManager.autoLogin.user to be set 378 ''; 379 } 380 { 381 assertion = cfg.desktopManager.default != null || cfg.windowManager.default != null -> cfg.displayManager.defaultSession == defaultSessionFromLegacyOptions; 382 message = "You cannot use both services.xserver.displayManager.defaultSession option and legacy options (services.xserver.desktopManager.default and services.xserver.windowManager.default)."; 383 } 384 ]; 385 386 warnings = 387 mkIf (dmDefault != null || wmDefault != null) [ 388 '' 389 The following options are deprecated: 390 ${concatStringsSep "\n " (map ({c, t}: t) (filter ({c, t}: c != null) [ 391 { c = dmDefault; t = "- services.xserver.desktopManager.default"; } 392 { c = wmDefault; t = "- services.xserver.windowManager.default"; } 393 ]))} 394 Please use 395 services.xserver.displayManager.defaultSession = "${defaultSessionFromLegacyOptions}"; 396 instead. 397 '' 398 ]; 399 400 services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X"; 401 402 services.xserver.displayManager.importedVariables = [ 403 # This is required by user units using the session bus. 404 "DBUS_SESSION_BUS_ADDRESS" 405 # These are needed by the ssh-agent unit. 406 "DISPLAY" 407 "XAUTHORITY" 408 # This is required to specify session within user units (e.g. loginctl lock-session). 409 "XDG_SESSION_ID" 410 ]; 411 412 systemd.user.targets.graphical-session = { 413 unitConfig = { 414 RefuseManualStart = false; 415 StopWhenUnneeded = false; 416 }; 417 }; 418 419 # Create desktop files and scripts for starting sessions for WMs/DMs 420 # that do not have upstream session files (those defined using services.{display,desktop,window}Manager.session options). 421 services.xserver.displayManager.sessionPackages = 422 let 423 dms = filter (s: s.manage == "desktop") cfg.displayManager.session; 424 wms = filter (s: s.manage == "window") cfg.displayManager.session; 425 426 # Script responsible for starting the window manager and the desktop manager. 427 xsession = dm: wm: pkgs.writeScript "xsession" '' 428 #! ${pkgs.bash}/bin/bash 429 430 # Legacy session script used to construct .desktop files from 431 # `services.xserver.displayManager.session` entries. Called from 432 # `sessionWrapper`. 433 434 # Start the window manager. 435 ${wm.start} 436 437 # Start the desktop manager. 438 ${dm.start} 439 440 ${optionalString cfg.updateDbusEnvironment '' 441 ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all 442 ''} 443 444 test -n "$waitPID" && wait "$waitPID" 445 446 /run/current-system/systemd/bin/systemctl --user stop graphical-session.target 447 448 exit 0 449 ''; 450 in 451 # We will generate every possible pair of WM and DM. 452 concatLists ( 453 builtins.map 454 ({dm, wm}: let 455 sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}"; 456 script = xsession dm wm; 457 desktopNames = if dm ? desktopNames 458 then concatStringsSep ";" dm.desktopNames 459 else sessionName; 460 in 461 optional (dm.name != "none" || wm.name != "none") 462 (pkgs.writeTextFile { 463 name = "${sessionName}-xsession"; 464 destination = "/share/xsessions/${sessionName}.desktop"; 465 # Desktop Entry Specification: 466 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ 467 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html 468 text = '' 469 [Desktop Entry] 470 Version=1.0 471 Type=XSession 472 TryExec=${script} 473 Exec=${script} 474 Name=${sessionName} 475 DesktopNames=${desktopNames} 476 ''; 477 } // { 478 providedSessions = [ sessionName ]; 479 }) 480 ) 481 (cartesianProductOfSets { dm = dms; wm = wms; }) 482 ); 483 484 # Make xsessions and wayland sessions available in XDG_DATA_DIRS 485 # as some programs have behavior that depends on them being present 486 environment.sessionVariables.XDG_DATA_DIRS = [ 487 "${cfg.displayManager.sessionData.desktops}/share" 488 ]; 489 }; 490 491 imports = [ 492 (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ] 493 "The option is no longer necessary because all display managers have already delegated lid management to systemd.") 494 (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ]) 495 (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ]) 496 (mkRenamedOptionModule [ "services" "xserver" "displayManager" "extraSessionFilesPackages" ] [ "services" "xserver" "displayManager" "sessionPackages" ]) 497 ]; 498 499}