{ config, pkgs, lib, utils, ... }: let cfg = config.wm.gnome; settingsFormat = pkgs.formats.ini { }; configFile = settingsFormat.generate "custom.conf" cfg.gdm.settings; nixos-background-info = pkgs.writeTextFile rec { name = "nixos-background-info"; destination = "/share/gnome-background-properties/nixos.xml"; text = '' Blobs ${pkgs.nixos-artwork.wallpapers.simple-blue.gnomeFilePath} ${pkgs.nixos-artwork.wallpapers.simple-dark-gray.gnomeFilePath} zoom solid #3a4ba0 #2f302f ''; }; in { options = { wm.gnome = { enable = lib.mkEnableOption "gnome"; gdm = { dconfSettings = lib.mkOption { type = lib.types.attrs; default = { }; }; settings = lib.mkOption { type = settingsFormat.type; default = { }; }; }; }; }; config = lib.mkIf cfg.enable { # patched to remove xorg and xwayland completely nixpkgs.overlays = [(final: prev: { mutter = prev.mutter.overrideAttrs( prevAttrs: { mesonFlags = [ "-Dinstalled_tests=false" "-Dtests=disabled" "-Ddocs=true" "-Dx11=false" "-Dxwayland=false" "-Degl_device=true" "-Dwayland_eglstream=true" "-Dwayland=true" "-Dprofiler=true" "-Dsm=false" ]; buildInputs = (utils.removePackagesByName prevAttrs.buildInputs [ prev.xorg.libSM prev.xwayland prev.gtk4 prev.xorg.libICE prev.xorg.libX11 prev.xorg.libXcomposite prev.xorg.libXcursor prev.xorg.libXdamage prev.xorg.libXext prev.xorg.libXfixes prev.xorg.libXi prev.xorg.libXtst prev.xorg.libxkbfile prev.xkeyboard_config prev.xorg.libxcb prev.xorg.libXrandr prev.xorg.libXinerama prev.xorg.libXau ]) ++ [ prev.libGL ]; nativeBuildInputs = utils.removePackagesByName prevAttrs.nativeBuildInputs [ prev.xorg.xorgserver ]; }); gdm = prev.gdm.overrideAttrs( prevAttrs: { mesonFlags = prev.lib.lists.remove "--Dgdm-xsession=true" (prevAttrs.mesonFlags ++ [ "-Dgdm-xsession=false" "-Dx11-support=false" ]); patches = [ # GDM fails to find g-s with the following error in the journal. # gdm-x-session[976]: dbus-run-session: failed to exec 'gnome-session': No such file or directory # https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/92 (prev.fetchpatch { url = "https://gitlab.gnome.org/GNOME/gdm/-/commit/ccecd9c975d04da80db4cd547b67a1a94fa83292.patch"; hash = "sha256-5hKS9wjjhuSAYwXct5vS0dPbmPRIINJoLC0Zm1naz6Q="; revert = true; }) ../../packages/patches/gdm-fix-wayland.patch # Change hardcoded paths to nix store paths. (prev.substituteAll { src = ../../packages/patches/gdm-fix-paths.patch; coreutils = final.coreutils; plymouth = final.plymouth; dbus = final.dbus; }) ]; postPatch = '' # Reverts https://gitlab.gnome.org/GNOME/gdm/-/commit/b0f802e36ff948a415bfd2bccaa268b6990515b7 # The gdm-auth-config tool is probably not too useful for NixOS, but we still want the dconf profile # installed (mostly just because .passthru.tests can make use of it). substituteInPlace meson.build \ --replace-fail "dconf_prefix = dconf_dep.get_variable(pkgconfig: 'prefix')" "dconf_prefix = gdm_prefix" ''; buildInputs = utils.removePackagesByName prevAttrs.buildInputs [ prev.xorg.libX11 prev.xorg.libXdmcp prev.xorg.libxcb ]; }); gnome-session = prev.gnome-session.overrideAttrs( prevAttrs: { mesonFlags = [ "-Dx11=false" ]; buildInputs = utils.removePackagesByName prevAttrs.buildInputs [ prev.xorg.libICE prev.xorg.xtrans ]; }); })]; users.groups.gdm.gid = config.ids.gids.gdm; users.users.gdm = { name = "gdm"; uid = config.ids.uids.gdm; group = "gdm"; home = "/run/gdm"; description = "GDM user"; }; security.polkit.enable = true; networking.networkmanager.enable = lib.mkDefault true; hardware = { graphics.enable = true; bluetooth.enable = lib.mkDefault true; }; fonts.packages = with pkgs; [ cantarell-fonts dejavu_fonts source-code-pro source-sans ]; environment = { etc."gdm/custom.conf".source = configFile; systemPackages = with pkgs; [ (lib.mkIf config.hardware.bluetooth.enable gnome-bluetooth) (lib.mkIf config.services.colord.enable gnome-color-manager) gnome-shell gnome-control-center ghostty adwaita-icon-theme sound-theme-freedesktop nixos-icons nixos-background-info glib # for gsettings program gnome-menus gtk3.out # for gtk-launch program xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/ xdg-user-dirs-gtk ]; # Needed for themes and backgrounds pathsToLink = [ "/share" # TODO: https://github.com/NixOS/nixpkgs/issues/47173 "/share/nautilus-python/extensions" ]; }; services = { gnome.gnome-settings-daemon.enable = true; gnome.glib-networking.enable = true; gvfs.enable = true; udisks2.enable = true; libinput.enable = true; accounts-daemon.enable = true; gnome.at-spi2-core.enable = lib.mkDefault true; gnome.gnome-keyring.enable = lib.mkDefault true; pipewire.enable = lib.mkDefault true; hardware.bolt.enable = lib.mkDefault true; colord.enable = lib.mkDefault true; power-profiles-daemon.enable = lib.mkDefault true; upower.enable = lib.mkDefault config.powerManagement.enable; system-config-printer.enable = lib.mkDefault config.services.printing.enable; udev.packages = [ pkgs.mutter ]; dbus.packages = [ pkgs.gdm ]; geoclue2.enable = lib.mkDefault true; geoclue2.enableDemoAgent = false; # GNOME has its own geoclue agent geoclue2.appConfig = lib.genAttrs [ "gnome-datetime-panel" "gnome-color-panel" "org.gnome.Shell" ] (name: { isAllowed = true; isSystem = true; }); }; programs = { dconf.enable = true; dconf.profiles.gdm.databases = [ { settings = cfg.gdm.dconfSettings; } "${pkgs.gdm}/share/gdm/greeter-dconf-defaults" ]; }; xdg = { mime.enable = true; icons.enable = true; portal.enable = true; portal.configPackages = lib.mkDefault [ pkgs.gnome-session ]; portal.extraPortals = with pkgs; [ xdg-desktop-portal-gnome xdg-desktop-portal-gtk ]; }; systemd = { user.services.dbus.wantedBy = [ "default.target" ]; tmpfiles.rules = [ "d /run/gdm/.config 0711 gdm gdm" ]; packages = with pkgs; [ gdm gnome-session gnome-shell ]; # We dont use the upstream gdm service # it has to be disabled since the gdm package has it # https://github.com/NixOS/nixpkgs/issues/108672 services.gdm.enable = false; services.display-manager = { description = "Display Manager"; wants = [ "systemd-machined.service" "accounts-daemon.service" ]; conflicts = [ "getty@${pkgs.gdm.initialVT}.service" "plymouth-quit.service" ]; onFailure = [ "plymouth-quit.service" ]; wantedBy = [ "multi-user.target" ]; after = [ "systemd-logind.service" "systemd-user-sessions.service" "systemd-machined.service" "getty@${pkgs.gdm.initialVT}.service" "acpid.service" "plymouth-quit.service" "plymouth-start.service" ]; path = [ pkgs.gnome-session ]; environment = { XDG_DATA_DIRS = lib.makeSearchPath "share" (with pkgs; [ gdm gnome-session.sessions gnome-control-center # for accessibility icon adwaita-icon-theme hicolor-icon-theme ]); }; serviceConfig = { KillMode = "mixed"; IgnoreSIGPIPE = "no"; BusName = "org.gnome.DisplayManager"; StandardError = "inherit"; ExecStart = "${pkgs.gdm}/bin/gdm"; ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; KeyringMode = "shared"; EnvironmentFile = "-/etc/locale.conf"; Restart = "always"; RestartSec = "200ms"; SyslogIdentifier = "display-manager"; }; restartIfChanged = false; # Stop restarting if the display manager stops (crashes) 2 times in one minute. startLimitIntervalSec = 30; startLimitBurst = 3; }; # Prevent nixos-rebuild switch from bringing down the graphical # session. (If multi-user.target wants plymouth-quit.service which # conflicts display-manager.service, then when nixos-rebuild # switch starts multi-user.target, display-manager.service is # stopped so plymouth-quit.service can be started.) services.plymouth-quit = lib.mkIf config.boot.plymouth.enable { wantedBy = lib.mkForce []; }; }; # GDM LFS PAM modules, adapted somehow to NixOS security.pam.services = { gdm-launch-environment.text = '' auth required pam_succeed_if.so audit quiet_success user = gdm auth optional pam_permit.so account required pam_succeed_if.so audit quiet_success user = gdm account sufficient pam_unix.so password required pam_deny.so session required pam_succeed_if.so audit quiet_success user = gdm 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 ''; gdm-password.text = '' auth substack login account include login password substack login session include login ''; gdm-autologin.text = '' auth requisite pam_nologin.so auth required pam_succeed_if.so uid >= 1000 quiet ${lib.optionalString config.security.pam.services.login.enableGnomeKeyring '' auth [success=ok default=1] ${pkgs.gdm}/lib/security/pam_gdm.so auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so ''} auth required pam_permit.so account sufficient pam_unix.so password requisite pam_unix.so nullok yescrypt session optional pam_keyinit.so revoke session include login ''; # This would block password prompt when included by gdm-password. # GDM will instead run gdm-fingerprint in parallel. login.fprintAuth = lib.mkIf config.services.fprintd.enable false; gdm-fingerprint.text = lib.mkIf config.services.fprintd.enable '' auth required pam_shells.so auth requisite pam_nologin.so auth requisite pam_faillock.so preauth auth required ${pkgs.fprintd}/lib/security/pam_fprintd.so auth required pam_env.so ${lib.optionalString config.security.pam.services.login.enableGnomeKeyring '' auth [success=ok default=1] ${pkgs.gdm}/lib/security/pam_gdm.so auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so ''} account include login password required pam_deny.so session include login ''; }; }; }