Gnome 3 on NixOS

NixOS can be configured to run any desktop environment you want and Gnome 3 is not an exception. However, it comes with some caveats so keep reading if you are interested in making this duo work seamlessly.

Users who enjoy a graphical environment normally like to tweak it with their own preferences as well. E.g. installing new extensions, changing the background image, changing the dock, etc. These are some of the tasks that Gnome Tweaks makes possible in Gnome 3.

Except, if you are in NixOS, I’m guessing you’d like to have all these tweaks declared in a configuration file so the next time you rebuild your system they are preserved, am I right?

NixOS configuration

First of all, we want to enable Gnome 3 as our default desktop manager in our /etc/nixos/configuration.nix file.

services = {
  xserver = {
    enable = true;
    layout = "us";
    displayManager.gdm.enable = true;
    displayManager.gdm.wayland = false;
    desktopManager.gnome3.enable = true;
  };

  dbus.packages = [ pkgs.gnome3.dconf ];
  udev.packages = [ pkgs.gnome3.gnome-settings-daemon ];
};

Wayland is a modern replacement for X. I tried it out for a while and it worked pretty well but unfortunately some functionality like screen-sharing is broken, reason why I have it disabled.

Additionally, you need dconf and gnome-settings-daemon running as a service to configure Gnome 3. This is all we need at the system level.

Home Manager

Home Manager is a great tool that manages all the software you want to install at the user level and all the configuration that comes with it. For example, Vim, Fish shell, Tmux, etc.

It can also manage Gnome extensions for us. Here’s an example:

{ config, pkgs, stdenv, lib, ... }:

{
  programs.home-manager.enable = true;

  home.packages = with pkgs; [
    # gnome3 apps
    gnome3.eog    # image viewer
    gnome3.evince # pdf reader

    # desktop look & feel
    gnome3.gnome-tweak-tool

    # extensions
    gnomeExtensions.appindicator
    gnomeExtensions.dash-to-dock
  ];

After running home-manager switch, if the build succeeded, you will see these extensions installed in Gnome Tweaks. Now we can go ahead, turn them on and configure each extension as we like. Next day we install another extension, say gnomeExtensions.battery-status, and again run home-manager switch. You will find out that all the three extensions are now installed but that you have lost the changes made to the configuration of the extensions!

As I previously mentioned, Gnome 3’s configuration is managed by dconf. You can get a description of the configuration by running the following command:

$ dconf dump / > dconf.settings

It will have content similar to the one below.

[ org/gnome/desktop/peripherals/mouse ]
natural-scroll=false
speed=-0.5

[ org/gnome/desktop/peripherals/touchpad ]
tap-to-click=false
two-finger-scrolling-enabled=true

[org/gnome/desktop/input-sources]
current=uint32 0
sources=[('xkb', 'us')]
xkb-options=[' terminate:ctrl_alt_bksp ', ' lv3:ralt_switch ', ' caps:ctrl_modifier ']

[ org/gnome/desktop/screensaver ]
picture-uri=' file:///home/gvolpe/Pictures/nixos.png '

Home Manager provides a dconf.settings option we can use to configure dconf. However, we need to write it in Nix instead. The configuration above will look as follows:

{ lib, ... }:

let
  mkTuple = lib.hm.gvariant.mkTuple;
in
{
  dconf.settings = {
    "org/gnome/desktop/peripherals/mouse" = {
      "natural-scroll" = false;
      "speed" = -0.5;
    };

    "org/gnome/desktop/peripherals/touchpad" = {
      "tap-to-click" = false;
      "two-finger-scrolling-enabled" = true;
    };

    "org/gnome/desktop/input-sources" = {
      "current" = "uint32 0";
      "sources" = [ (mkTuple [ "xkb" "us" ]) ];
      "xkb-options" = [ "terminate:ctrl_alt_bksp" "lv3:ralt_switch" "caps:ctrl_modifier" ];
    };

    "org/gnome/desktop/screensaver" = {
      "picture-uri" = "file:///home/gvolpe/Pictures/nixos.png";
    };
  };
}

As we can see, it is a bit tedious to write all of this manually.

dconf2nix

So I recently made available a tool called dconf2nix, which automates this conversion. It is written in a few lines of Haskell, thanks to the expressiveness of parser combinators.

Once you get the dump of the dconf configuration, you’re just a command away from getting it in the Nix format as expected by Home Manager.

$ dconf2nix -i dconf.settings -o dconf.nix

To keep the modularity, it is a good practice to keep the dconf.nix as is, and import it in your home.nix file instead.

{ config, pkgs, stdenv, lib, ... }:

{
  programs.home-manager.enable = true;

  imports = [ ./dconf.nix ];
}

And that’s it! Next time you make changes in the UI, make sure to also update the dconf.nix file. For instance, you may want to enable some extensions by default.

"org/gnome/shell" = {
  command-history = [ "gnome-tweaks" ];
  enabled-extensions = [
    "horizontal-workspaces@gnome-shell-extensions.gcampax.github.com"
    "dash-to-dock@micxgx.gmail.com"
  ];
  favorite-apps = [
    "chromium-browser.desktop"
    "spotify.desktop"
    "org.gnome.tweaks.desktop"
  ];
};

You can have a look at my NixOS configuration, where I have also configured some custom Gnome extensions and a bunch of other stuff.

Have fun with Nix! And if you happen to find any issue with dconf2nix, please report it, as it is quite new and there might be some missing edge cases.

Cheers, Gabriel.