Commit e1c30820 authored by rnhmjoj's avatar rnhmjoj Committed by Valentin Gagarin
Browse files

nixos/startx: try to improve UX

There are some common pitfalls and no documentation around how to write
the .xinitrc to correctly start the window manager, the systemd
graphical session and, ideally, cleaning up afterwards.

To improve the user experience around startx this change:

1. Adds two options to generate a sane default script and extend
   it declaratively from NixOS.

2. Adds assertions to graphical-session.target so that it will fail
   clearly and immediately when users writing their own script forget to
   import the necessary environment variables.
parent 7252fbc5
Loading
Loading
Loading
Loading
+73 −16
Original line number Diff line number Diff line
@@ -5,12 +5,16 @@
  ...
}:

with lib;

let

  cfg = config.services.xserver.displayManager.startx;

  # WM session script
  # Note: this assumes a single WM has been enabled
  sessionScript = lib.concatMapStringsSep "\n" (
    i: i.start
  ) config.services.xserver.windowManager.session;

in

{
@@ -19,40 +23,93 @@ in

  options = {
    services.xserver.displayManager.startx = {
      enable = mkOption {
        type = types.bool;
      enable = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Whether to enable the dummy "startx" pseudo-display manager,
          which allows users to start X manually via the "startx" command
          from a vt shell. The X server runs under the user's id, not as root.
          The user must provide a ~/.xinitrc file containing session startup
          commands, see {manpage}`startx(1)`. This is not automatically generated
          from the desktopManager and windowManager settings.
          Whether to enable the dummy "startx" pseudo-display manager, which
          allows users to start X manually via the `startx` command from a
          virtual terminal.

          ::: {.note}
          The X server will run under the current user, not as root.
          :::
        '';
      };

      generateScript = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Whether to generate the system-wide xinitrc script (/etc/X11/xinit/xinitrc).
          This script will take care of setting up the session for systemd user
          services, running the window manager and cleaning up on exit.

          ::: {.note}
          This script will only be used by `startx` when both `.xinitrc` does not
          exists and the `XINITRC` environment variable is unset.
          :::
        '';
      };

      extraCommands = lib.mkOption {
        type = lib.types.lines;
        default = "";
        description = ''
          Shell commands to be added to the system-wide xinitrc script.
        '';
      };

    };
  };

  ###### implementation

  config = mkIf cfg.enable {
    services.xserver = {
      exportConfiguration = true;
    };
  config = lib.mkIf cfg.enable {
    services.xserver.exportConfiguration = true;

    # Other displayManagers log to /dev/null because they're services and put
    # Xorg's stdout in the journal
    #
    # To send log to Xorg's default log location ($XDG_DATA_HOME/xorg/), we do
    # not specify a log file when running X
    services.xserver.logFile = mkDefault null;
    services.xserver.logFile = lib.mkDefault null;

    # Implement xserverArgs via xinit's system-wide xserverrc
    environment.etc."X11/xinit/xserverrc".source = pkgs.writeShellScript "xserverrc" ''
      exec ${pkgs.xorg.xorgserver}/bin/X ${toString config.services.xserver.displayManager.xserverArgs} "$@"
      exec ${pkgs.xorg.xorgserver}/bin/X \
        ${toString config.services.xserver.displayManager.xserverArgs} "$@"
    '';

    # Add a sane system-wide xinitrc script
    environment.etc."X11/xinit/xinitrc".source = lib.mkIf cfg.generateScript (
      pkgs.writeShellScript "xinitrc" ''
        ${cfg.extraCommands}

        # start user services
        systemctl --user import-environment DISPLAY XDG_SESSION_ID
        systemctl --user start nixos-fake-graphical-session.target

        # run the window manager script
        ${sessionScript}
        wait $waitPID

        # stop services and all subprocesses
        systemctl --user stop nixos-fake-graphical-session.target
        kill 0
      ''
    );

    environment.systemPackages = with pkgs; [ xorg.xinit ];

    # Make graphical-session fail if the user environment has not been imported
    systemd.user.targets.graphical-session = {
      unitConfig.AssertEnvironment = [
        "DISPLAY"
        "XDG_SESSION_ID"
      ];
    };

  };

}