{ config, lib, pkgs, ... }: let xcfg = config.services.xserver; dmcfg = config.services.displayManager; cfg = config.services.displayManager.sddm; xEnv = config.systemd.services.display-manager.environment; sddm = cfg.package.override (old: { withWayland = cfg.wayland.enable; withLayerShellQt = cfg.wayland.compositor == "kwin"; extraPackages = old.extraPackages or [ ] ++ cfg.extraPackages; }); iniFmt = pkgs.formats.ini { }; inherit (lib) concatMapStrings concatStringsSep getExe attrNames getAttr optionalAttrs optionalString mkRemovedOptionModule mkRenamedOptionModule mkIf mkEnableOption mkOption mkPackageOption types ; xserverWrapper = pkgs.writeShellScript "xserver-wrapper" '' ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)} exec systemd-cat -t xserver-wrapper ${xcfg.displayManager.xserverBin} ${toString xcfg.displayManager.xserverArgs} "$@" ''; Xsetup = pkgs.writeShellScript "Xsetup" '' ${cfg.setupScript} ${xcfg.displayManager.setupCommands} ''; Xstop = pkgs.writeShellScript "Xstop" '' ${cfg.stopScript} ''; defaultConfig = { General = { HaltCommand = "/run/current-system/systemd/bin/systemctl poweroff"; RebootCommand = "/run/current-system/systemd/bin/systemctl reboot"; Numlock = if cfg.autoNumlock then "on" else "none"; # on, off none # Implementation is done via pkgs/applications/display-managers/sddm/sddm-default-session.patch DefaultSession = optionalString (config.services.displayManager.defaultSession != null) "${config.services.displayManager.defaultSession}.desktop"; DisplayServer = if cfg.wayland.enable then "wayland" else "x11"; } // optionalAttrs (cfg.wayland.enable && cfg.wayland.compositor == "kwin") { GreeterEnvironment = "QT_WAYLAND_SHELL_INTEGRATION=layer-shell"; InputMethod = ""; # needed if we are using --inputmethod with kwin }; Theme = { Current = cfg.theme; ThemeDir = "/run/current-system/sw/share/sddm/themes"; FacesDir = "/run/current-system/sw/share/sddm/faces"; } // optionalAttrs (cfg.theme == "breeze") { CursorTheme = "breeze_cursors"; CursorSize = 24; }; Users = { MaximumUid = config.ids.uids.nixbld; HideUsers = concatStringsSep "," dmcfg.hiddenUsers; HideShells = "/run/current-system/sw/bin/nologin"; }; Wayland = { EnableHiDPI = cfg.enableHidpi; SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions"; CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand; }; } // optionalAttrs xcfg.enable { X11 = { MinimumVT = if xcfg.tty != null then xcfg.tty else 7; ServerPath = toString xserverWrapper; XephyrPath = "${pkgs.xorg.xorgserver.out}/bin/Xephyr"; SessionCommand = toString dmcfg.sessionData.wrapper; SessionDir = "${dmcfg.sessionData.desktops}/share/xsessions"; XauthPath = "${pkgs.xorg.xauth}/bin/xauth"; DisplayCommand = toString Xsetup; DisplayStopCommand = toString Xstop; EnableHiDPI = cfg.enableHidpi; }; } // optionalAttrs dmcfg.autoLogin.enable { Autologin = { User = dmcfg.autoLogin.user; Session = autoLoginSessionName; Relogin = cfg.autoLogin.relogin; }; }; cfgFile = iniFmt.generate "sddm.conf" (lib.recursiveUpdate defaultConfig cfg.settings); autoLoginSessionName = "${dmcfg.sessionData.autologinSession}.desktop"; compositorCmds = { kwin = concatStringsSep " " [ "${lib.getBin pkgs.kdePackages.kwin}/bin/kwin_wayland" "--no-global-shortcuts" "--no-kactivities" "--no-lockscreen" "--locale1" ]; # This is basically the upstream default, but with Weston referenced by full path # and the configuration generated from NixOS options. weston = let westonIni = (pkgs.formats.ini { }).generate "weston.ini" { libinput = { enable-tap = config.services.libinput.mouse.tapping; left-handed = config.services.libinput.mouse.leftHanded; }; keyboard = { keymap_model = xcfg.xkb.model; keymap_layout = xcfg.xkb.layout; keymap_variant = xcfg.xkb.variant; keymap_options = xcfg.xkb.options; }; }; in "${getExe pkgs.weston} --shell=kiosk -c ${westonIni}"; }; in { imports = [ (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoLogin" "minimumUid" ] [ "services" "displayManager" "sddm" "autoLogin" "minimumUid" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoLogin" "relogin" ] [ "services" "displayManager" "sddm" "autoLogin" "relogin" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoNumlock" ] [ "services" "displayManager" "sddm" "autoNumlock" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "enable" ] [ "services" "displayManager" "sddm" "enable" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "enableHidpi" ] [ "services" "displayManager" "sddm" "enableHidpi" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "extraPackages" ] [ "services" "displayManager" "sddm" "extraPackages" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "package" ] [ "services" "displayManager" "sddm" "package" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "settings" ] [ "services" "displayManager" "sddm" "settings" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "setupScript" ] [ "services" "displayManager" "sddm" "setupScript" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "stopScript" ] [ "services" "displayManager" "sddm" "stopScript" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "theme" ] [ "services" "displayManager" "sddm" "theme" ]) (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "wayland" "enable" ] [ "services" "displayManager" "sddm" "wayland" "enable" ]) (mkRemovedOptionModule [ "services" "displayManager" "sddm" "themes" ] "Set the option `services.displayManager.sddm.package' instead.") (mkRenamedOptionModule [ "services" "displayManager" "sddm" "autoLogin" "enable" ] [ "services" "displayManager" "autoLogin" "enable" ]) (mkRenamedOptionModule [ "services" "displayManager" "sddm" "autoLogin" "user" ] [ "services" "displayManager" "autoLogin" "user" ]) (mkRemovedOptionModule [ "services" "displayManager" "sddm" "extraConfig" ] "Set the option `services.displayManager.sddm.settings' instead.") ]; options = { services.displayManager.sddm = { enable = mkOption { type = types.bool; default = false; description = '' Whether to enable sddm as the display manager. ''; }; package = mkPackageOption pkgs [ "plasma5Packages" "sddm" ] { }; enableHidpi = mkOption { type = types.bool; default = true; description = '' Whether to enable automatic HiDPI mode. ''; }; settings = mkOption { type = iniFmt.type; default = { }; example = { Autologin = { User = "john"; Session = "plasma.desktop"; }; }; description = '' Extra settings merged in and overwriting defaults in sddm.conf. ''; }; theme = mkOption { type = types.str; default = ""; description = '' Greeter theme to use. ''; }; extraPackages = mkOption { type = types.listOf types.package; default = [ ]; defaultText = "[]"; description = '' Extra Qt plugins / QML libraries to add to the environment. ''; }; autoNumlock = mkOption { type = types.bool; default = false; description = '' Enable numlock at login. ''; }; setupScript = mkOption { type = types.str; default = ""; example = '' # workaround for using NVIDIA Optimus without Bumblebee xrandr --setprovideroutputsource modesetting NVIDIA-0 xrandr --auto ''; description = '' A script to execute when starting the display server. DEPRECATED, please use {option}`services.xserver.displayManager.setupCommands`. ''; }; stopScript = mkOption { type = types.str; default = ""; description = '' A script to execute when stopping the display server. ''; }; # Configuration for automatic login specific to SDDM autoLogin = { relogin = mkOption { type = types.bool; default = false; description = '' If true automatic login will kick in again on session exit (logout), otherwise it will only log in automatically when the display-manager is started. ''; }; minimumUid = mkOption { type = types.ints.u16; default = 1000; description = '' Minimum user ID for auto-login user. ''; }; }; # Experimental Wayland support wayland = { enable = mkEnableOption "experimental Wayland support"; compositor = mkOption { description = "The compositor to use: ${lib.concatStringsSep ", " (builtins.attrNames compositorCmds)}"; type = types.enum (builtins.attrNames compositorCmds); default = "weston"; }; compositorCommand = mkOption { type = types.str; internal = true; default = compositorCmds.${cfg.wayland.compositor}; description = "Command used to start the selected compositor"; }; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = xcfg.enable || cfg.wayland.enable; message = '' SDDM requires either services.xserver.enable or services.displayManager.sddm.wayland.enable to be true ''; } { assertion = config.services.displayManager.autoLogin.enable -> autoLoginSessionName != null; message = '' SDDM auto-login requires that services.displayManager.defaultSession is set. ''; } ]; services.displayManager = { enable = true; execCmd = "exec /run/current-system/sw/bin/sddm"; }; security.pam.services = { sddm.text = '' auth substack login account include login password substack login session include login ''; sddm-greeter.text = '' auth required pam_succeed_if.so audit quiet_success user = sddm auth optional pam_permit.so account required pam_succeed_if.so audit quiet_success user = sddm account sufficient pam_unix.so password required pam_deny.so session required pam_succeed_if.so audit quiet_success user = sddm session required pam_env.so conffile=/etc/pam/environment readenv=0 session optional ${config.systemd.package}/lib/security/pam_systemd.so session optional pam_keyinit.so force revoke session optional pam_permit.so ''; sddm-autologin.text = '' auth requisite pam_nologin.so auth required pam_succeed_if.so uid >= ${toString cfg.autoLogin.minimumUid} quiet auth required pam_permit.so account include sddm password include sddm session include sddm ''; }; users.users.sddm = { createHome = true; home = "/var/lib/sddm"; group = "sddm"; uid = config.ids.uids.sddm; }; environment = { etc."sddm.conf".source = cfgFile; pathsToLink = [ "/share/sddm" ]; systemPackages = [ sddm ]; }; users.groups.sddm.gid = config.ids.gids.sddm; services = { dbus.packages = [ sddm ]; xserver = { # To enable user switching, allow sddm to allocate TTYs/displays dynamically. tty = null; display = null; }; }; systemd = { tmpfiles.packages = [ sddm ]; # We're not using the upstream unit, so copy these: https://github.com/sddm/sddm/blob/develop/services/sddm.service.in services.display-manager = { after = [ "systemd-user-sessions.service" "getty@tty7.service" "plymouth-quit.service" "systemd-logind.service" ]; conflicts = [ "getty@tty7.service" ]; }; }; }; }