at 24.11-pre 12 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, options, pkgs, ... }: 11 12let 13 inherit (lib) mkOption types literalExpression optionalString; 14 15 cfg = config.services.xserver; 16 xorg = pkgs.xorg; 17 18 fontconfig = config.fonts.fontconfig; 19 xresourcesXft = pkgs.writeText "Xresources-Xft" '' 20 Xft.antialias: ${if fontconfig.antialias then "1" else "0"} 21 Xft.rgba: ${fontconfig.subpixel.rgba} 22 Xft.lcdfilter: lcd${fontconfig.subpixel.lcdfilter} 23 Xft.hinting: ${if fontconfig.hinting.enable then "1" else "0"} 24 Xft.autohint: ${if fontconfig.hinting.autohint then "1" else "0"} 25 Xft.hintstyle: ${fontconfig.hinting.style} 26 ''; 27 28 # FIXME: this is an ugly hack. 29 # Some sessions (read: most WMs) don't activate systemd's `graphical-session.target`. 30 # Other sessions (read: most non-WMs) expect `graphical-session.target` to be reached 31 # when the entire session is actually ready. We used to just unconditionally force 32 # `graphical-session.target` to be activated in the session wrapper so things like 33 # xdg-autostart-generator work on sessions that are wrong, but this broke sessions 34 # that do things right. So, preserve this behavior (with some extra steps) by matching 35 # on XDG_CURRENT_DESKTOP and deliberately ignoring sessions we know can do the right thing. 36 fakeSession = action: '' 37 session_is_systemd_aware=$( 38 IFS=: 39 for i in $XDG_CURRENT_DESKTOP; do 40 case $i in 41 KDE|GNOME|Pantheon|X-NIXOS-SYSTEMD-AWARE) echo "1"; exit; ;; 42 *) ;; 43 esac 44 done 45 ) 46 47 if [ -z "$session_is_systemd_aware" ]; then 48 /run/current-system/systemd/bin/systemctl --user ${action} nixos-fake-graphical-session.target 49 fi 50 ''; 51 52 # file provided by services.xserver.displayManager.sessionData.wrapper 53 xsessionWrapper = pkgs.writeScript "xsession-wrapper" 54 '' 55 #! ${pkgs.bash}/bin/bash 56 57 # Shared environment setup for graphical sessions. 58 59 . /etc/profile 60 if test -f ~/.profile; then 61 source ~/.profile 62 fi 63 64 cd "$HOME" 65 66 # Allow the user to execute commands at the beginning of the X session. 67 if test -f ~/.xprofile; then 68 source ~/.xprofile 69 fi 70 71 ${optionalString config.services.displayManager.logToJournal '' 72 if [ -z "$_DID_SYSTEMD_CAT" ]; then 73 export _DID_SYSTEMD_CAT=1 74 exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$@" 75 fi 76 ''} 77 78 ${optionalString config.services.displayManager.logToFile '' 79 exec &> >(tee ~/.xsession-errors) 80 ''} 81 82 # Load X defaults. This should probably be safe on wayland too. 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 # Import environment variables into the systemd user environment. 91 ${optionalString (cfg.displayManager.importedVariables != []) ( 92 "/run/current-system/systemd/bin/systemctl --user import-environment " 93 + toString (lib.unique cfg.displayManager.importedVariables) 94 )} 95 96 # Speed up application start by 50-150ms according to 97 # https://kdemonkey.blogspot.com/2008/04/magic-trick.html 98 compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}" 99 mkdir -p "$compose_cache" 100 # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory, 101 # defensively try to delete cache *files* only, following the file format specified in 102 # https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/modules/im/ximcp/imLcIm.c#L353-358 103 # sprintf (*res, "%s/%c%d_%03x_%08x_%08x", dir, _XimGetMyEndian(), XIM_CACHE_VERSION, (unsigned int)sizeof (DefTree), hash, hash2); 104 ${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 105 unset compose_cache 106 107 # Work around KDE errors when a user first logs in and 108 # .local/share doesn't exist yet. 109 mkdir -p "''${XDG_DATA_HOME:-$HOME/.local/share}" 110 111 unset _DID_SYSTEMD_CAT 112 113 ${cfg.displayManager.sessionCommands} 114 115 ${fakeSession "start"} 116 117 # Allow the user to setup a custom session type. 118 if test -x ~/.xsession; then 119 eval exec ~/.xsession "$@" 120 fi 121 122 if test "$1"; then 123 # Run the supplied session command. Remove any double quotes with eval. 124 eval exec "$@" 125 else 126 # TODO: Do we need this? Should not the session always exist? 127 echo "error: unknown session $1" 1>&2 128 exit 1 129 fi 130 ''; 131in 132 133{ 134 options = { 135 136 services.xserver.displayManager = { 137 138 xauthBin = mkOption { 139 internal = true; 140 default = "${xorg.xauth}/bin/xauth"; 141 defaultText = literalExpression ''"''${pkgs.xorg.xauth}/bin/xauth"''; 142 description = "Path to the {command}`xauth` program used by display managers."; 143 }; 144 145 xserverBin = mkOption { 146 type = types.path; 147 description = "Path to the X server used by display managers."; 148 }; 149 150 xserverArgs = mkOption { 151 type = types.listOf types.str; 152 default = []; 153 example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ]; 154 description = "List of arguments for the X server."; 155 }; 156 157 setupCommands = mkOption { 158 type = types.lines; 159 default = ""; 160 description = '' 161 Shell commands executed just after the X server has started. 162 163 This option is only effective for display managers for which this feature 164 is supported; currently these are LightDM, GDM and SDDM. 165 ''; 166 }; 167 168 sessionCommands = mkOption { 169 type = types.lines; 170 default = ""; 171 example = 172 '' 173 xmessage "Hello World!" & 174 ''; 175 description = '' 176 Shell commands executed just before the window or desktop manager is 177 started. These commands are not currently sourced for Wayland sessions. 178 ''; 179 }; 180 181 session = mkOption { 182 default = []; 183 type = types.listOf types.attrs; 184 example = literalExpression 185 '' 186 [ { manage = "desktop"; 187 name = "xterm"; 188 start = ''' 189 ''${pkgs.xterm}/bin/xterm -ls & 190 waitPID=$! 191 '''; 192 } 193 ] 194 ''; 195 description = '' 196 List of sessions supported with the command used to start each 197 session. Each session script can set the 198 {var}`waitPID` shell variable to make this script 199 wait until the end of the user session. Each script is used 200 to define either a window manager or a desktop manager. These 201 can be differentiated by setting the attribute 202 {var}`manage` either to `"window"` 203 or `"desktop"`. 204 205 The list of desktop manager and window manager should appear 206 inside the display manager with the desktop manager name 207 followed by the window manager name. 208 ''; 209 }; 210 211 importedVariables = mkOption { 212 type = types.listOf (types.strMatching "[a-zA-Z_][a-zA-Z0-9_]*"); 213 visible = false; 214 description = '' 215 Environment variables to import into the systemd user environment. 216 ''; 217 }; 218 219 }; 220 221 }; 222 223 config = { 224 services.displayManager.sessionData.wrapper = xsessionWrapper; 225 226 services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X"; 227 228 services.xserver.displayManager.importedVariables = [ 229 # This is required by user units using the session bus. 230 "DBUS_SESSION_BUS_ADDRESS" 231 # These are needed by the ssh-agent unit. 232 "DISPLAY" 233 "XAUTHORITY" 234 # This is required to specify session within user units (e.g. loginctl lock-session). 235 "XDG_SESSION_ID" 236 ]; 237 238 systemd.user.targets.nixos-fake-graphical-session = { 239 unitConfig = { 240 Description = "Fake graphical-session target for non-systemd-aware sessions"; 241 BindsTo = "graphical-session.target"; 242 }; 243 }; 244 245 # Create desktop files and scripts for starting sessions for WMs/DMs 246 # that do not have upstream session files (those defined using services.{display,desktop,window}Manager.session options). 247 services.displayManager.sessionPackages = 248 let 249 dms = lib.filter (s: s.manage == "desktop") cfg.displayManager.session; 250 wms = lib.filter (s: s.manage == "window") cfg.displayManager.session; 251 252 # Script responsible for starting the window manager and the desktop manager. 253 xsession = dm: wm: pkgs.writeScript "xsession" '' 254 #! ${pkgs.bash}/bin/bash 255 256 # Legacy session script used to construct .desktop files from 257 # `services.xserver.displayManager.session` entries. Called from 258 # `sessionWrapper`. 259 260 # Start the window manager. 261 ${wm.start} 262 263 # Start the desktop manager. 264 ${dm.start} 265 266 ${optionalString cfg.updateDbusEnvironment '' 267 ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all 268 ''} 269 270 test -n "$waitPID" && wait "$waitPID" 271 272 ${fakeSession "stop"} 273 274 exit 0 275 ''; 276 in 277 # We will generate every possible pair of WM and DM. 278 lib.concatLists ( 279 lib.mapCartesianProduct 280 ({dm, wm}: let 281 sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}"; 282 script = xsession dm wm; 283 desktopNames = if dm ? desktopNames 284 then lib.concatStringsSep ";" dm.desktopNames 285 else sessionName; 286 in 287 lib.optional (dm.name != "none" || wm.name != "none") 288 (pkgs.writeTextFile { 289 name = "${sessionName}-xsession"; 290 destination = "/share/xsessions/${sessionName}.desktop"; 291 # Desktop Entry Specification: 292 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ 293 # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html 294 text = '' 295 [Desktop Entry] 296 Version=1.0 297 Type=XSession 298 TryExec=${script} 299 Exec=${script} 300 Name=${sessionName} 301 DesktopNames=${desktopNames} 302 ''; 303 } // { 304 providedSessions = [ sessionName ]; 305 }) 306 ) 307 { dm = dms; wm = wms; } 308 ); 309 }; 310 311 imports = [ 312 (lib.mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ] 313 "The option is no longer necessary because all display managers have already delegated lid management to systemd.") 314 (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "displayManager" "logToFile" ]) 315 (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "displayManager" "logToJournal" ]) 316 (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "extraSessionFilesPackages" ] [ "services" "displayManager" "sessionPackages" ]) 317 ]; 318 319}