diff options
author | Sandro <sandro.jaeckel@gmail.com> | 2024-04-09 14:31:12 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-09 14:31:12 +0200 |
commit | 4cc6ce454d3049ded63959b0c80f4c50dc6f89cc (patch) | |
tree | c45201fdbf22421881cf79cc4f2f1fe5f04d1a98 /nixos/modules/services/display-managers | |
parent | 1dd379340a2d99b85a039104291fb878e60cbe0c (diff) | |
parent | 476b8c276e2ae4c98efd1d8d759029e170c5ca98 (diff) |
Merge pull request #291913 from SuperSandro2000/sddm-wayland-only
Diffstat (limited to 'nixos/modules/services/display-managers')
-rw-r--r-- | nixos/modules/services/display-managers/default.nix | 257 | ||||
-rw-r--r-- | nixos/modules/services/display-managers/sddm.nix | 382 |
2 files changed, 639 insertions, 0 deletions
diff --git a/nixos/modules/services/display-managers/default.nix b/nixos/modules/services/display-managers/default.nix new file mode 100644 index 0000000000000..7f5db9fbb509b --- /dev/null +++ b/nixos/modules/services/display-managers/default.nix @@ -0,0 +1,257 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.displayManager; + + installedSessions = pkgs.runCommand "desktops" + { # trivial derivation + preferLocalBuild = true; + allowSubstitutes = false; + } + '' + mkdir -p "$out/share/"{xsessions,wayland-sessions} + + ${lib.concatMapStrings (pkg: '' + for n in ${lib.concatStringsSep " " pkg.providedSessions}; do + if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \ + -f ${pkg}/share/xsessions/$n.desktop; then + echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:" + echo " ${pkg}" + return 1 + fi + done + + if test -d ${pkg}/share/xsessions; then + ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions + fi + if test -d ${pkg}/share/wayland-sessions; then + ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions + fi + '') cfg.sessionPackages} + ''; + + dmDefault = config.services.xserver.desktopManager.default; + # fallback default for cases when only default wm is set + dmFallbackDefault = if dmDefault != null then dmDefault else "none"; + wmDefault = config.services.xserver.windowManager.default; + defaultSessionFromLegacyOptions = dmFallbackDefault + lib.optionalString (wmDefault != null && wmDefault != "none") "+${wmDefault}"; +in +{ + options = { + services.displayManager = { + enable = lib.mkEnableOption "systemd's display-manager service"; + + preStart = lib.mkOption { + type = lib.types.lines; + default = ""; + example = "rm -f /var/log/my-display-manager.log"; + description = lib.mdDoc "Script executed before the display manager is started."; + }; + + execCmd = lib.mkOption { + type = lib.types.str; + example = lib.literalExpression ''"''${pkgs.lightdm}/bin/lightdm"''; + description = lib.mdDoc "Command to start the display manager."; + }; + + environment = lib.mkOption { + type = with lib.types; attrsOf unspecified; + default = {}; + description = lib.mdDoc "Additional environment variables needed by the display manager."; + }; + + hiddenUsers = lib.mkOption { + type = with lib.types; listOf str; + default = [ "nobody" ]; + description = lib.mdDoc '' + A list of users which will not be shown in the display manager. + ''; + }; + + logToFile = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Whether the display manager redirects the output of the + session script to {file}`~/.xsession-errors`. + ''; + }; + + logToJournal = lib.mkOption { + type = lib.types.bool; + default = true; + description = lib.mdDoc '' + Whether the display manager redirects the output of the + session script to the systemd journal. + ''; + }; + + # Configuration for automatic login. Common for all DM. + autoLogin = lib.mkOption { + type = lib.types.submodule ({ config, options, ... }: { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.user != null; + defaultText = lib.literalExpression "config.${options.user} != null"; + description = lib.mdDoc '' + Automatically log in as {option}`autoLogin.user`. + ''; + }; + + user = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = lib.mdDoc '' + User to be used for the automatic login. + ''; + }; + }; + }); + + default = {}; + description = lib.mdDoc '' + Auto login configuration attrset. + ''; + }; + + defaultSession = lib.mkOption { + type = lib.types.nullOr lib.types.str // { + description = "session name"; + check = d: + lib.assertMsg (d != null -> (lib.types.str.check d && lib.elem d config.services.displayManager.sessionData.sessionNames)) '' + Default graphical session, '${d}', not found. + Valid names for 'services.displayManager.defaultSession' are: + ${lib.concatStringsSep "\n " cfg.displayManager.sessionData.sessionNames} + ''; + }; + default = + if dmDefault != null || wmDefault != null then + defaultSessionFromLegacyOptions + else + null; + defaultText = lib.literalMD '' + Taken from display manager settings or window manager settings, if either is set. + ''; + example = "gnome"; + description = lib.mdDoc '' + Graphical session to pre-select in the session chooser (only effective for GDM, LightDM and SDDM). + + On GDM, LightDM and SDDM, it will also be used as a session for auto-login. + ''; + }; + + sessionData = lib.mkOption { + description = lib.mdDoc "Data exported for display managers’ convenience"; + internal = true; + default = {}; + }; + + sessionPackages = lib.mkOption { + type = lib.types.listOf (lib.types.package // { + description = "package with provided sessions"; + check = p: lib.assertMsg + (lib.types.package.check p && p ? providedSessions + && p.providedSessions != [] && lib.all lib.isString p.providedSessions) + '' + Package, '${p.name}', did not specify any session names, as strings, in + 'passthru.providedSessions'. This is required when used as a session package. + + The session names can be looked up in: + ${p}/share/xsessions + ${p}/share/wayland-sessions + ''; + }); + default = []; + description = lib.mdDoc '' + A list of packages containing x11 or wayland session files to be passed to the display manager. + ''; + }; + }; + }; + + imports = [ + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "autoLogin" ] [ "services" "displayManager" "autoLogin" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "defaultSession" ] [ "services" "displayManager" "defaultSession" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "environment" ] [ "services" "displayManager" "environment" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "execCmd" ] [ "services" "displayManager" "execCmd" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logToFile" ] [ "services" "displayManager" "logToFile" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logToJournal" ] [ "services" "displayManager" "logToJournal" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "preStart" ] [ "services" "displayManager" "preStart" ]) + ]; + + config = lib.mkIf cfg.enable { + assertions = [ + { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null; + message = '' + services.displayManager.autoLogin.enable requires services.displayManager.autoLogin.user to be set + ''; + } + ]; + + warnings = + lib.mkIf (dmDefault != null || wmDefault != null) [ + '' + The following options are deprecated: + ${lib.concatStringsSep "\n " (map ({c, t}: t) (lib.filter ({c, t}: c != null) [ + { c = dmDefault; t = "- services.xserver.desktopManager.default"; } + { c = wmDefault; t = "- services.xserver.windowManager.default"; } + ]))} + Please use + services.displayManager.defaultSession = "${defaultSessionFromLegacyOptions}"; + instead. + '' + ]; + + # Make xsessions and wayland sessions available in XDG_DATA_DIRS + # as some programs have behavior that depends on them being present + environment.sessionVariables.XDG_DATA_DIRS = lib.mkIf (cfg.sessionPackages != [ ]) [ + "${cfg.sessionData.desktops}/share" + ]; + + services.displayManager.sessionData = { + desktops = installedSessions; + sessionNames = lib.concatMap (p: p.providedSessions) config.services.displayManager.sessionPackages; + # We do not want to force users to set defaultSession when they have only single DE. + autologinSession = + if cfg.defaultSession != null then + cfg.defaultSession + else if cfg.sessionData.sessionNames != [] then + lib.head cfg.sessionData.sessionNames + else + null; + }; + + # so that the service won't be enabled when only startx is used + systemd.services.display-manager.enable = + let dmConf = config.services.xserver.displayManager; + noDmUsed = !(dmConf.gdm.enable + || cfg.sddm.enable + || dmConf.xpra.enable + || dmConf.lightdm.enable); + in lib.mkIf noDmUsed (lib.mkDefault false); + + systemd.services.display-manager = { + description = "Display Manager"; + after = [ "acpid.service" "systemd-logind.service" "systemd-user-sessions.service" ]; + restartIfChanged = false; + + environment = lib.optionalAttrs config.hardware.opengl.setLdLibraryPath { + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.addOpenGLRunpath.driverLink ]; + } // cfg.environment; + + preStart = cfg.preStart; + script = lib.mkIf (config.systemd.services.display-manager.enable == true) cfg.execCmd; + + # Stop restarting if the display manager stops (crashes) 2 times + # in one minute. Starting X typically takes 3-4s. + startLimitIntervalSec = 30; + startLimitBurst = 3; + serviceConfig = { + Restart = "always"; + RestartSec = "200ms"; + SyslogIdentifier = "display-manager"; + }; + }; + }; +} diff --git a/nixos/modules/services/display-managers/sddm.nix b/nixos/modules/services/display-managers/sddm.nix new file mode 100644 index 0000000000000..d7bd26e779d9a --- /dev/null +++ b/nixos/modules/services/display-managers/sddm.nix @@ -0,0 +1,382 @@ +{ 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; + 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.compositor == "kwin") { + GreeterEnvironment = concatStringsSep " " [ + "LANG=C.UTF-8" + "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"; + }; + + 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; + }; + + Wayland = { + EnableHiDPI = cfg.enableHidpi; + SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions"; + CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand; + }; + } // 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 = xcfg.libinput.mouse.tapping; + left-handed = xcfg.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 = lib.mdDoc '' + Whether to enable sddm as the display manager. + ''; + }; + + package = mkPackageOption pkgs [ "plasma5Packages" "sddm" ] { }; + + enableHidpi = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to enable automatic HiDPI mode. + ''; + }; + + settings = mkOption { + type = iniFmt.type; + default = { }; + example = { + Autologin = { + User = "john"; + Session = "plasma.desktop"; + }; + }; + description = lib.mdDoc '' + Extra settings merged in and overwriting defaults in sddm.conf. + ''; + }; + + theme = mkOption { + type = types.str; + default = ""; + description = lib.mdDoc '' + Greeter theme to use. + ''; + }; + + extraPackages = mkOption { + type = types.listOf types.package; + default = [ ]; + defaultText = "[]"; + description = lib.mdDoc '' + Extra Qt plugins / QML libraries to add to the environment. + ''; + }; + + autoNumlock = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + 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 = lib.mdDoc '' + A script to execute when starting the display server. DEPRECATED, please + use {option}`services.xserver.displayManager.setupCommands`. + ''; + }; + + stopScript = mkOption { + type = types.str; + default = ""; + description = lib.mdDoc '' + 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 = lib.mdDoc '' + 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 = lib.mdDoc '' + Minimum user ID for auto-login user. + ''; + }; + }; + + # Experimental Wayland support + wayland = { + enable = mkEnableOption "experimental Wayland support"; + + compositor = mkOption { + description = lib.mdDoc "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 = lib.mdDoc "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" + ]; + }; + }; + }; +} |