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