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