1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 xcfg = config.services.xserver; 10 dmcfg = config.services.displayManager; 11 cfg = config.services.displayManager.sddm; 12 xEnv = config.systemd.services.display-manager.environment; 13 14 sddm = cfg.package.override (old: { 15 withWayland = cfg.wayland.enable; 16 withLayerShellQt = cfg.wayland.compositor == "kwin"; 17 extraPackages = old.extraPackages or [ ] ++ cfg.extraPackages; 18 }); 19 20 iniFmt = pkgs.formats.ini { }; 21 22 inherit (lib) 23 concatMapStrings 24 concatStringsSep 25 getExe 26 attrNames 27 getAttr 28 optionalAttrs 29 optionalString 30 mkRemovedOptionModule 31 mkRenamedOptionModule 32 mkIf 33 mkEnableOption 34 mkOption 35 mkPackageOption 36 types 37 ; 38 39 xserverWrapper = pkgs.writeShellScript "xserver-wrapper" '' 40 ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)} 41 exec systemd-cat -t xserver-wrapper ${xcfg.displayManager.xserverBin} ${toString xcfg.displayManager.xserverArgs} "$@" 42 ''; 43 44 Xsetup = pkgs.writeShellScript "Xsetup" '' 45 ${cfg.setupScript} 46 ${xcfg.displayManager.setupCommands} 47 ''; 48 49 Xstop = pkgs.writeShellScript "Xstop" '' 50 ${cfg.stopScript} 51 ''; 52 53 defaultConfig = 54 { 55 General = 56 { 57 HaltCommand = "/run/current-system/systemd/bin/systemctl poweroff"; 58 RebootCommand = "/run/current-system/systemd/bin/systemctl reboot"; 59 Numlock = if cfg.autoNumlock then "on" else "none"; # on, off none 60 61 # Implementation is done via pkgs/applications/display-managers/sddm/sddm-default-session.patch 62 DefaultSession = optionalString ( 63 config.services.displayManager.defaultSession != null 64 ) "${config.services.displayManager.defaultSession}.desktop"; 65 66 DisplayServer = if cfg.wayland.enable then "wayland" else "x11"; 67 } 68 // optionalAttrs (cfg.wayland.enable && cfg.wayland.compositor == "kwin") { 69 GreeterEnvironment = "QT_WAYLAND_SHELL_INTEGRATION=layer-shell"; 70 InputMethod = ""; # needed if we are using --inputmethod with kwin 71 }; 72 73 Theme = 74 { 75 Current = cfg.theme; 76 ThemeDir = "/run/current-system/sw/share/sddm/themes"; 77 FacesDir = "/run/current-system/sw/share/sddm/faces"; 78 } 79 // optionalAttrs (cfg.theme == "breeze") { 80 CursorTheme = "breeze_cursors"; 81 CursorSize = 24; 82 }; 83 84 Users = { 85 MaximumUid = config.ids.uids.nixbld; 86 HideUsers = concatStringsSep "," dmcfg.hiddenUsers; 87 HideShells = "/run/current-system/sw/bin/nologin"; 88 }; 89 90 Wayland = { 91 EnableHiDPI = cfg.enableHidpi; 92 SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions"; 93 CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand; 94 }; 95 96 } 97 // optionalAttrs xcfg.enable { 98 X11 = { 99 MinimumVT = if xcfg.tty != null then xcfg.tty else 7; 100 ServerPath = toString xserverWrapper; 101 XephyrPath = "${pkgs.xorg.xorgserver.out}/bin/Xephyr"; 102 SessionCommand = toString dmcfg.sessionData.wrapper; 103 SessionDir = "${dmcfg.sessionData.desktops}/share/xsessions"; 104 XauthPath = "${pkgs.xorg.xauth}/bin/xauth"; 105 DisplayCommand = toString Xsetup; 106 DisplayStopCommand = toString Xstop; 107 EnableHiDPI = cfg.enableHidpi; 108 }; 109 } 110 // optionalAttrs dmcfg.autoLogin.enable { 111 Autologin = { 112 User = dmcfg.autoLogin.user; 113 Session = autoLoginSessionName; 114 Relogin = cfg.autoLogin.relogin; 115 }; 116 }; 117 118 cfgFile = iniFmt.generate "sddm.conf" (lib.recursiveUpdate defaultConfig cfg.settings); 119 120 autoLoginSessionName = "${dmcfg.sessionData.autologinSession}.desktop"; 121 122 compositorCmds = { 123 kwin = concatStringsSep " " [ 124 "${lib.getBin pkgs.kdePackages.kwin}/bin/kwin_wayland" 125 "--no-global-shortcuts" 126 "--no-kactivities" 127 "--no-lockscreen" 128 "--locale1" 129 ]; 130 # This is basically the upstream default, but with Weston referenced by full path 131 # and the configuration generated from NixOS options. 132 weston = 133 let 134 westonIni = (pkgs.formats.ini { }).generate "weston.ini" { 135 libinput = { 136 enable-tap = config.services.libinput.mouse.tapping; 137 left-handed = config.services.libinput.mouse.leftHanded; 138 }; 139 keyboard = { 140 keymap_model = xcfg.xkb.model; 141 keymap_layout = xcfg.xkb.layout; 142 keymap_variant = xcfg.xkb.variant; 143 keymap_options = xcfg.xkb.options; 144 }; 145 }; 146 in 147 "${getExe pkgs.weston} --shell=kiosk -c ${westonIni}"; 148 }; 149 150in 151{ 152 imports = [ 153 (mkRenamedOptionModule 154 [ "services" "xserver" "displayManager" "sddm" "autoLogin" "minimumUid" ] 155 [ "services" "displayManager" "sddm" "autoLogin" "minimumUid" ] 156 ) 157 (mkRenamedOptionModule 158 [ "services" "xserver" "displayManager" "sddm" "autoLogin" "relogin" ] 159 [ "services" "displayManager" "sddm" "autoLogin" "relogin" ] 160 ) 161 (mkRenamedOptionModule 162 [ "services" "xserver" "displayManager" "sddm" "autoNumlock" ] 163 [ "services" "displayManager" "sddm" "autoNumlock" ] 164 ) 165 (mkRenamedOptionModule 166 [ "services" "xserver" "displayManager" "sddm" "enable" ] 167 [ "services" "displayManager" "sddm" "enable" ] 168 ) 169 (mkRenamedOptionModule 170 [ "services" "xserver" "displayManager" "sddm" "enableHidpi" ] 171 [ "services" "displayManager" "sddm" "enableHidpi" ] 172 ) 173 (mkRenamedOptionModule 174 [ "services" "xserver" "displayManager" "sddm" "extraPackages" ] 175 [ "services" "displayManager" "sddm" "extraPackages" ] 176 ) 177 (mkRenamedOptionModule 178 [ "services" "xserver" "displayManager" "sddm" "package" ] 179 [ "services" "displayManager" "sddm" "package" ] 180 ) 181 (mkRenamedOptionModule 182 [ "services" "xserver" "displayManager" "sddm" "settings" ] 183 [ "services" "displayManager" "sddm" "settings" ] 184 ) 185 (mkRenamedOptionModule 186 [ "services" "xserver" "displayManager" "sddm" "setupScript" ] 187 [ "services" "displayManager" "sddm" "setupScript" ] 188 ) 189 (mkRenamedOptionModule 190 [ "services" "xserver" "displayManager" "sddm" "stopScript" ] 191 [ "services" "displayManager" "sddm" "stopScript" ] 192 ) 193 (mkRenamedOptionModule 194 [ "services" "xserver" "displayManager" "sddm" "theme" ] 195 [ "services" "displayManager" "sddm" "theme" ] 196 ) 197 (mkRenamedOptionModule 198 [ "services" "xserver" "displayManager" "sddm" "wayland" "enable" ] 199 [ "services" "displayManager" "sddm" "wayland" "enable" ] 200 ) 201 202 (mkRemovedOptionModule [ 203 "services" 204 "displayManager" 205 "sddm" 206 "themes" 207 ] "Set the option `services.displayManager.sddm.package' instead.") 208 (mkRenamedOptionModule 209 [ "services" "displayManager" "sddm" "autoLogin" "enable" ] 210 [ "services" "displayManager" "autoLogin" "enable" ] 211 ) 212 (mkRenamedOptionModule 213 [ "services" "displayManager" "sddm" "autoLogin" "user" ] 214 [ "services" "displayManager" "autoLogin" "user" ] 215 ) 216 (mkRemovedOptionModule [ 217 "services" 218 "displayManager" 219 "sddm" 220 "extraConfig" 221 ] "Set the option `services.displayManager.sddm.settings' instead.") 222 ]; 223 224 options = { 225 226 services.displayManager.sddm = { 227 enable = mkOption { 228 type = types.bool; 229 default = false; 230 description = '' 231 Whether to enable sddm as the display manager. 232 ''; 233 }; 234 235 package = mkPackageOption pkgs [ "plasma5Packages" "sddm" ] { }; 236 237 enableHidpi = mkOption { 238 type = types.bool; 239 default = true; 240 description = '' 241 Whether to enable automatic HiDPI mode. 242 ''; 243 }; 244 245 settings = mkOption { 246 type = iniFmt.type; 247 default = { }; 248 example = { 249 Autologin = { 250 User = "john"; 251 Session = "plasma.desktop"; 252 }; 253 }; 254 description = '' 255 Extra settings merged in and overwriting defaults in sddm.conf. 256 ''; 257 }; 258 259 theme = mkOption { 260 type = types.str; 261 default = ""; 262 description = '' 263 Greeter theme to use. 264 ''; 265 }; 266 267 extraPackages = mkOption { 268 type = types.listOf types.package; 269 default = [ ]; 270 defaultText = "[]"; 271 description = '' 272 Extra Qt plugins / QML libraries to add to the environment. 273 ''; 274 }; 275 276 autoNumlock = mkOption { 277 type = types.bool; 278 default = false; 279 description = '' 280 Enable numlock at login. 281 ''; 282 }; 283 284 setupScript = mkOption { 285 type = types.str; 286 default = ""; 287 example = '' 288 # workaround for using NVIDIA Optimus without Bumblebee 289 xrandr --setprovideroutputsource modesetting NVIDIA-0 290 xrandr --auto 291 ''; 292 description = '' 293 A script to execute when starting the display server. DEPRECATED, please 294 use {option}`services.xserver.displayManager.setupCommands`. 295 ''; 296 }; 297 298 stopScript = mkOption { 299 type = types.str; 300 default = ""; 301 description = '' 302 A script to execute when stopping the display server. 303 ''; 304 }; 305 306 # Configuration for automatic login specific to SDDM 307 autoLogin = { 308 relogin = mkOption { 309 type = types.bool; 310 default = false; 311 description = '' 312 If true automatic login will kick in again on session exit (logout), otherwise it 313 will only log in automatically when the display-manager is started. 314 ''; 315 }; 316 317 minimumUid = mkOption { 318 type = types.ints.u16; 319 default = 1000; 320 description = '' 321 Minimum user ID for auto-login user. 322 ''; 323 }; 324 }; 325 326 # Experimental Wayland support 327 wayland = { 328 enable = mkEnableOption "experimental Wayland support"; 329 330 compositor = mkOption { 331 description = "The compositor to use: ${lib.concatStringsSep ", " (builtins.attrNames compositorCmds)}"; 332 type = types.enum (builtins.attrNames compositorCmds); 333 default = "weston"; 334 }; 335 336 compositorCommand = mkOption { 337 type = types.str; 338 internal = true; 339 default = compositorCmds.${cfg.wayland.compositor}; 340 description = "Command used to start the selected compositor"; 341 }; 342 }; 343 }; 344 }; 345 346 config = mkIf cfg.enable { 347 348 assertions = [ 349 { 350 assertion = xcfg.enable || cfg.wayland.enable; 351 message = '' 352 SDDM requires either services.xserver.enable or services.displayManager.sddm.wayland.enable to be true 353 ''; 354 } 355 { 356 assertion = config.services.displayManager.autoLogin.enable -> autoLoginSessionName != null; 357 message = '' 358 SDDM auto-login requires that services.displayManager.defaultSession is set. 359 ''; 360 } 361 ]; 362 363 services.displayManager = { 364 enable = true; 365 execCmd = "exec /run/current-system/sw/bin/sddm"; 366 }; 367 368 security.pam.services = { 369 sddm.text = '' 370 auth substack login 371 account include login 372 password substack login 373 session include login 374 ''; 375 376 sddm-greeter.text = '' 377 auth required pam_succeed_if.so audit quiet_success user = sddm 378 auth optional pam_permit.so 379 380 account required pam_succeed_if.so audit quiet_success user = sddm 381 account sufficient pam_unix.so 382 383 password required pam_deny.so 384 385 session required pam_succeed_if.so audit quiet_success user = sddm 386 session required pam_env.so conffile=/etc/pam/environment readenv=0 387 session optional ${config.systemd.package}/lib/security/pam_systemd.so 388 session optional pam_keyinit.so force revoke 389 session optional pam_permit.so 390 ''; 391 392 sddm-autologin.text = '' 393 auth requisite pam_nologin.so 394 auth required pam_succeed_if.so uid >= ${toString cfg.autoLogin.minimumUid} quiet 395 auth required pam_permit.so 396 397 account include sddm 398 399 password include sddm 400 401 session include sddm 402 ''; 403 }; 404 405 users.users.sddm = { 406 createHome = true; 407 home = "/var/lib/sddm"; 408 group = "sddm"; 409 uid = config.ids.uids.sddm; 410 }; 411 412 environment = { 413 etc."sddm.conf".source = cfgFile; 414 pathsToLink = [ 415 "/share/sddm" 416 ]; 417 systemPackages = [ sddm ]; 418 }; 419 420 users.groups.sddm.gid = config.ids.gids.sddm; 421 422 services = { 423 dbus.packages = [ sddm ]; 424 xserver = { 425 # To enable user switching, allow sddm to allocate TTYs/displays dynamically. 426 tty = null; 427 display = null; 428 }; 429 }; 430 431 systemd = { 432 tmpfiles.packages = [ sddm ]; 433 434 # We're not using the upstream unit, so copy these: https://github.com/sddm/sddm/blob/develop/services/sddm.service.in 435 services.display-manager = { 436 after = [ 437 "systemd-user-sessions.service" 438 "getty@tty7.service" 439 "plymouth-quit.service" 440 "systemd-logind.service" 441 ]; 442 conflicts = [ 443 "getty@tty7.service" 444 ]; 445 }; 446 }; 447 }; 448}