Monitor Mode Switching at Log in/out

Hello!

I am a new nixos user having some difficulty with display managers, so apologies if my descriptions aren’t helpful, and just let me know what additional information I should provide. I’m running nixos version 26.05, I have unfree nvidia drivers enabled as described on the wiki (https://nixos.wiki/wiki/Nvidia), and I’m using sway (enabled both as a nixos module and a home-manager module).

What currently happens:

Every time I log in or out, my monitor turns black, flickers, and then turns back on as normal. The sequence takes about 2-3 seconds. I recognize this exact behavior from back when I had windows installed: it happens every time I change the resolution or refresh rate on my monitor. This behavior started immediately after I enabled the nvidia drivers.

What should happen:

When logging in, sway should load immediately without the monitor going black. I know this behavior is possible since it’s what happened before I had the nvidia drivers enabled. When logging out, sway should immediately lock, and the log in screen should immediately appear. sway should also run at 2560x1440@143.856Hz.

What I’ve tried:

I’ve tried disabling the nvidia drivers, but this leads to quickshell and noctalia crashing when browsing certain pages on Firefox (YouTube). With the nvidia drivers install, I’ve tried:

  • Enabling services such as greetd to get the login screen to run at 2560x1440@143.856Hz.
  • Setting boot.kernelParams = [ “video=DP-1:2560x1440@143.856” ]; to get the kernel to run at 2560x1440@143.856Hz.
  • Launching greetd in its own sway session configured to run at 2560x1440@143.856Hz.

Unfortunately, none of the above have worked out for me. I’ve copied the relevant portions of my configuration below. Thanks in advance for any help!

As a nixos module, I have:

# display.nix
{ config, pkgs, lib, ... }:
  let
    res = "2560x1440";
    rate = "143.856";

    greetdSwayConfig = pkgs.writeText "greetd-sway-config" ''
      output "DP-1" mode ${res}@${rate}Hz
      
      exec "${pkgs.greetd.regreet}/bin/regreet; swaymsg exit"
    '';
  in {
    programs.sway.enable = true;
    security.polkit.enable = true;  # needed for `sway`

    boot.kernelParams = [ 
      "video=DP-1:${res}@${rate}"
    ];

    programs.regreet.enable = true;

    services.greetd = {
      enable = true;
      settings.default_session.command = lib.mkForce "${pkgs.sway}/bin/sway --unsupported-gpu --config ${greetdSwayConfig}";
    };
}

As a home-manager module, I have:

# sway.nix
{ config, lib, pkgs, ... }: {
  wayland.windowManager.sway = {
    enable = true;

    wrapperFeatures.gtk = true;  # recommended by the nixos wiki to fix common issue with GTK 3 apps

    extraOptions = [
      "--unsupported-gpu"  # since sway doesn't support unfree nvidia drivers
    ];

    config = {
      terminal = "kitty";

      startup = [
        {
          command = "swaymsg workspace number 1";
          always = false;
        }
        { command = "noctalia-shell"; }
      ];

      output = {
        "DP-1" = {
          mode = "2560x1440@143.856Hz";
        };
      };
    };
  };
}

The nvidia drivers are also enabled through nixos modules:

# nvidia.nix
# (almost) straight copy-paste from https://nixos.wiki/wiki/Nvidia.
{ config, lib, pkgs, ... }: {
  nixpkgs.config.allowUnfreePredicate = pkg:
    builtins.elem (lib.getName pkg) [
      "nvidia-x11"
      "nvidia-settings"
      "nvidia-persistenced"
    ];

  # Enable OpenGL
  hardware.graphics = {
    enable = true;
  };

  # Load nvidia driver for Xorg and Wayland
  services.xserver.videoDrivers = [ "nvidia" ];

  hardware.nvidia = {

    # Modesetting is required.
    modesetting.enable = true;

    # Nvidia power management. Experimental, and can cause sleep/suspend to fail.
    # Enable this if you have graphical corruption issues or application crashes after waking
    # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead 
    # of just the bare essentials.
    powerManagement.enable = false;

    # Fine-grained power management. Turns off GPU when not in use.
    # Experimental and only works on modern Nvidia GPUs (Turing or newer).
    powerManagement.finegrained = false;

    # Use the NVidia open source kernel module (not to be confused with the
    # independent third-party "nouveau" open source driver).
    # Support is limited to the Turing and later architectures. Full list of 
    # supported GPUs is at: 
    # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus 
    # Only available from driver 515.43.04+
    open = false;

    # Enable the Nvidia settings menu,
	  # accessible via `nvidia-settings`.
    nvidiaSettings = true;

    # Optionally, you may need to select the appropriate driver version for your specific GPU.
    package = config.boot.kernelPackages.nvidiaPackages.stable;
  };
}
1 Like

This is known as modesetting. Since you use greetd, every login/logout passes through the tty. I’m guessing that this forces a modeset during that pass:

Most likely the monitor’s preferred mode isn’t precisely what you list there, and regreet (or the compositor it renders with) has some functionality to set the monitor’s preferred mode, causing you to have a modeset on every instantiation of regreet or the tty.

I would just remove that kernel param, and use firmware settings to set a default display mode to use at boot.

For the sake of argument, I’d remove all configuration for the monitor’s mode, for the moment, including your settings for sway.


The NixOS wiki is full of stupidity around the nvidia driver, by the way, people cargo cult without knowing anything about the driver and then push their experiments back up to the wiki, without understanding - let alone explaining - why their config doesn’t apply to others.

You haven’t specified your hardware, so I will assume a reasonably modern (RTX 2xxx+) GPU and no iGPU. Use this instead of your current nvidia.nix:

# nvidia.nix
#  not copied from the wiki because the wiki sucks.
{ config, lib, pkgs, ... }: {
  nixpkgs.config.allowUnfreePredicate = pkg:
    builtins.elem (lib.getName pkg) [
      "nvidia-x11"
      "nvidia-settings"
      "nvidia-persistenced"
    ];

  # Enable OpenGL *if* it doesn't work ootb
  # Regreet's module should already do so.
  # hardware.graphics = {
  #   enable = true;
  # };

  # Load nvidia driver for Xorg and Wayland
  services.xserver.videoDrivers = [ "nvidia" ];

  hardware.nvidia = {
    # This setting is required for sleep to work on
    # modern GPUs. It's also no longer experimental
    powerManagement.enable = true;

    # nvidia has all but stopped maintaining the
    # non-open driver; if your GPU supports it, you
    # should be using the open one.
    open = true;

    # All other settings are redundant for a Turing+
    # GPU on a dGPU-only system.
  };
}

As an aside, I highly recommend using programs.uwsm with sway.

As it stands, your configuration does not correctly initialize your session, which will result in subtle integration bugs down the line.

1 Like

Sorry I think I accidentally didn’t make my post a response to yours. Here’s what I meant to say:

Thanks for your response! Something that I should have shared at the beginning is: I’m actually using a GeForce 1080. It still works really well (on windows) so I’m hoping I get get it to work on linux as well. Per your advice, I’ve cut out everything in my config that has to do with manually settings the monitor’s mode. I was only using greetd as a potential fix to this issue, so I’ve cut that as well. I’ve cut nvidia.nix down as well, and enabled uwsm. Unfortunately however, the issue persists. Using swaymsg -t get_outputs, I can see that my monitor now runs at 2560x1440 @ 59.951 Hz instead of at 143.856 Hz (both of these are listed as available modes, but I guess 59.951 Hz is preferred).

To correctly initialize my session, is uwsm all that’s needed, or are there other things I’m still missing (I’m still very new at this; apologies).

Is there anything else that could be causing this? sway is being configured both as a nixos module and as a home-manager module; could that be creating some sort of conflict?

Thanks again for any tips or pointers!

My config is now:

# display.nix
{ config, ... }: {
  programs.sway.enable = true;
  security.polkit.enable = true;  # needed for sway

  programs.uwsm = {
    waylandCompositors = {
      sway = {
        prettyName = "Sway";
        comment = "Sway compositor managed by UWSM";
        binPath = "/run/current-system/sw/bin/sway";
      };
    };
  };
}
# nvidia.nix
{ config, lib, pkgs, ... }: {
  nixpkgs.config.allowUnfreePredicate = pkg:
    builtins.elem (lib.getName pkg) [
      "nvidia-x11"
      "nvidia-settings"
      "nvidia-persistenced"
    ];

  services.xserver.videoDrivers = [ "nvidia" ];

  hardware.nvidia = {
    powerManagement.enable = true;
    open = false;  # since I'm using a 1080?
  };
}

Mostly, early in your sway config you should call uwsm finalize, since sway doesn’t by itself import vars into systemd.

You can also use uwsm’s application management features, both to get desktop apps properly scoped in systemd scopes, and to do autostart with systemd service dependencies, but that’s not essential.

That’s unfortunate. Theoretically switching between the same mode shouldn’t cause a flicker. I guess nouveau detects that that’s what you’re doing, and skips sending the relevant commands, while nvidia doesn’t. You might have to complain to nvidia about this one, but it’s unlikely you’ll get much of a response for a GPU that old. It’s also possible a different monitor would be smart enough to ignore the noop modeswitch; that, or your monitor’s EDID is not good, but hunting down a patched EDID is very difficult, let alone hand-patching it.

The only way around this with your current hardware is to not do modeswitches at all, but this unfortunately requires a full system compositor, which no current wayland compositor is. X11 WMs/DEs did launch in the same X11 instance as the login manager, but that’s not how the wayland stack is generally architected. DEs like Plasma/Gnome decided to keep their display managers separate, because they wanted to be able to conveniently switch between X11 and wayland sessions, and all the WMs prefer not designing around display managers at all.

I personally would like to design a setup with a wayland-based login manager, probably based on river, precisely because the extra modeswitch still creates a few black frames even with better-behaving drivers.

Yes, since home-manager will attempt to manage your sway binaries, but the actually running instance is launched from the sway managed by NixOS. This, for example, makes the wrapperFeatures.gtk thing nonfunctional, that has to be done on the NixOS module.

Though I’m not sure I would recommend using either of the wrapperFeatures - the base variant just does a subset of what uwsm does, and the gtk variant actively breaks nixpkgs’ gtk isolation, just so that you can run gsettings commands inside your sway config. Having read through the wrapper, I think I’m tempted to switch to using sway-unwrapped.

I’d suggest looking at the home-manager module code, and configuring your home-manager module in such a way that it doesn’t trigger any of the settings that manage the sway binaries. That, or you just write your sway configuration by hand and use xdg.configFile to symlink it into place.

This is definitely not the cause of your modesetting issues, though.

1 Like

I think dms-greeter does this. It’s a quick-shell based greeter that an chain into another wayland-based compositor. They have some NixOS config on that page.

I also thought that the new plasma-login-manager forked from SDDM should be able to do this, but I can’t find any doc that suggests this is possible in their ecosystem.

It is incredibly annoying how slow modern monitors are just to turn on their screens or switch resolutions, when laptop panels show how quick and painless it should be. I wish reviews and comparison sites would start reporting this figure so manufacturers would finally start designing for it.

I don’t see anything in their repo or in quickshell suggesting that it can avoid a modeset while flicking through the tty. It’s also greetd-based, so that will happen.

I have read that if you technically break the rules and simply don’t call a certain syscall while relinquishing control of the display it could be done, if this greeter does that then yeah, maybe. It’s possible quickshell explicitly supports that. But it’s definitely not a system compositor.

Thanks so much for the tip! If I include no mention of resolution or refresh rate in my nixos and home-manager config, adding the following does not result in a modeset at log in/out:

# dms_greeter.nix
{ inputs, config, machine, ... }: {
  imports = [
    inputs.dms.nixosModules.greeter  # also requires adding dms to inputs
  ];
  programs.dank-material-shell.greeter = {
    enable = true;
    compositor = {
      name = "sway";
    };
  };
}
# sway.nix nixos module, not home-manager module
{ config, ... }: {
  programs.sway = {
    enable = true;
    extraOptions = [
      "--unsupported-gpu"
    ];
  };
}

However, this results in my monitor defaulting to 2560x1440@60Hz instead of 144Hz unfortunately. If I add:

# inside dms_greeter.nix
programs.dank-material-shell.greeter.compositor = {
  customConfig = ''
    output "DP-1" {
      mode 2560x1440@143.856Hz
    }
  '';
};
# inside sway.nix, home-manager module
output = {
  "DP-1" = {
    mode = "2560x1440@143.856Hz";
  };
};

Then there’s a modeset at log in/out. Is there something extra that has to be done to enabling the chaining your described? I’ll take a closer look at the docs myself too.

That is super cool!

It’s already working, but if you add a modeset to the things it’s supposed to do by hand clearly it will still do so.

I think this is where your kernel settings can help, this should run early enough that you don’t notice:

1 Like