EXWM: use gccEmacs as window manager (override Emacs binary)

Hi,

I use EXWM as my window manager. I want to change which version of Emacs it uses, but I can’t figure out how to do it. I’d like to use the native-comp branch (or gccEmacs/emacsGcc) which is available in the nix-community/emacs-overlay. Is this possible and, if so, what’s the recommended way of doing it?

I’ve found and tried to follow this gist on how to set it up, but it doesn’t say how to make it work for EXWM. The windowManager.exwm config available also does not have an option to specify which emacs you want it to use. So from what I gather, the best way to make this work would be to override emacs attribute of pkgs and replace it with emacsGcc from the overlay. The problem is I don’t know how.

This thread makes me think it’s possible, but I wasn’t able to figure out how to transfer that solution to my problem.

Any input is much appreciated. Cheers!

There doesn’t seem to be a way to do this with the windowManager.exwm configuration (adding a windowManager.exwm.package option might be a good target for a pull request).
It looks like all that module really does is sets the services.xserver.windowManager.session to start emacsWithPackages that includes exwm with a custom script to load exwm.

You could construct your customized emacs and set the services.xserver.windowManager.session to a script that starts your emacs.

1 Like

Ah, that’s both good and bad news at the same time! The bad news being that there’s no ‘simple’ way to do it, but the good news being that it doesn’t look that hard either. Thanks!

I’m also very intrigued by the idea of opening up a pull request and making the change. I found this issue on GitHub, which asks for something similar (though something that would be a breaking change), but it doesn’t seem to have gotten a response in the ~3 months it’s been open. What’s the usual development speed on this? Anyway, I’ll ask over there as well and see what I can do about it.

I’m not quite sure what the contributing process looks like; I haven’t made any myself yet! My PR comment was directed to me as much as you :wink:

If you give a pull request a chance I’d be interested to see how it goes

Kind of messy but I do this: https://github.com/codygman/hci/blob/6c07ce2d4c60382f892e7ddc1d5441cf2286041b/nixpkgs/home.nix#L21

Hey! It’s been a couple days, but I did finally get around to at least
trying to get some work done on this.

I tried copying the EXWM nixpkgs module and changing it for my own
purposes. This seems to have worked, except for the fact that I
couldn’t get it to build. There was a build error that appeared when
compiling Emacs. Don’t know whether it’s worth anything, but it built
just fine when using nixos-rebuild build-vm instead of
nixos-rebuild switch. Any thoughts on that, @mattchrist? If not,
you’re free to see if this works for you. Based on the issue mentioned
above, I thought it’d be nice if the module would let you build
packages with Emacs, so I expect something like the following:


let
  my-exwm-emacs = pkgs.emacsWithPackagesFromUsePackage {
    config = /home/<user>/.emacs.d/init.el;
    package = pkgs.emacsGcc;
  };

  exwm-load-script = ''
    (require 'exwm)
    (exwm-init)
  '';


in {
  services.xserver.windowManager.myExwm = {
      enable = true;
      enableDefaultConfig = false;
      executable = my-exwm-emacs;
      loadScript = exwm-load-script;
  };
}
Customized exwm module
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.xserver.windowManager.myExwm;
  loadScript = pkgs.writeText "emacs-exwm-load" ''
    ${cfg.loadScript}
    ${optionalString cfg.enableDefaultConfig ''
      (require 'exwm-config)
      (exwm-config-default)
    ''}
  '';
  exwm-emacs = cfg.executable;

in {
  options = {
    services.xserver.windowManager.myExwm = {
      enable = mkEnableOption "exwm";
      loadScript = mkOption {
        default = "(require 'exwm)";
        example = literalExample ''
          (require 'exwm)
          (exwm-enable)
        '';
        description = ''
          Emacs lisp code to be run after loading the user's init
          file. If enableDefaultConfig is true, this will be run
          before loading the default config.
        '';
      };
      enableDefaultConfig = mkOption {
        default = true;
        type = lib.types.bool;
        description = "Enable an uncustomised exwm configuration.";
      };
      executable = mkOption {
        default = pkgs.emacsWithPackages (epkgs: [ epkgs.exwm ]);
        example = literalExample ''
          emacsWithPackagesFromUsePackage {
                config = ./init.el;
                package = pkgs.emacsGit;
          };
        '';
        description = ''
          Which emacs executable to use, including packages.
        '';
      };
    };
  };

  config = mkIf cfg.enable {
    services.xserver.windowManager.session = singleton {
      name = "exwm";
      start = ''
        ${exwm-emacs}/bin/emacs -l ${loadScript}
      '';
    };
    environment.systemPackages = [ exwm-emacs ];
  };
}

@codygman Thanks for your input too! I’ve never used home-manager
before, but gave it a little go now. Unfortunately I’m not quite
familiar enough with Nix to understand how it all works and hangs
together, so I don’t quite understand how to make it work with my
window manager, though I did see the section on graphical
services

in the readme. I’ll play around with it some more later, though. I’d
ask a few questions, but I’m not sure where to start yet, so I’ll save
that for later if you don’t mind.

Cheers!

1 Like

I’m not sure why it would build with build-vm but not switch – those eval nearly the same configuration… Can you share the error you’re getting?

For what it’s worth, I’m also using home-manager to configure exwm & emacs: https://git.sr.ht/~mchrist/dotfiles/tree/master/item/editors/emacs/default.nix#L121

Yeah, of course! Sorry I didn’t think of that yesterday. I might have been a bit tired after spending so much time on this. Anyway, this is what I get at the end of the build (from a little before the errors show up):

Build output (end)
Dumping fingerprint: 0b3b48f6e8cc07f743e661d631f16529073db556ba51ca44bbf6d8e5e541c3f2
Dump complete
Byte counts: header=100 hot=8789020 discardable=172464 cold=4942616
Reloc counts: hot=510283 discardable=4960
Adding name emacs-28.0.50.1
Adding name emacs-28.0.50.1.pdmp
cp -f emacs.pdmp bootstrap-emacs.pdmp
make[1]: Leaving directory '/build/source/src'
make -C lisp all
make[1]: Entering directory '/build/source/lisp'
make -C ../leim all EMACS="../src/emacs"
make -C ../admin/grammars all EMACS="../../src/emacs"
make[2]: Entering directory '/build/source/admin/grammars'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/build/source/admin/grammars'
  GEN      cus-load.el
make[2]: Entering directory '/build/source/leim'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/build/source/leim'
  GEN      finder-inf.el
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
make[1]: *** [Makefile:169: cus-load.el] Error 1
make[1]: *** Waiting for unfinished jobs....
make[1]: *** [Makefile:177: finder-inf.el] Error 1
make[2]: Entering directory '/build/source/lisp'
  ELC      emacs-lisp/eieio.elc
  ELC      emacs-lisp/eieio-base.elc
  ELC      cedet/semantic/db.elc
  ELC      align.elc
  ELC      allout-widgets.elc
  ELC      allout.elc
  ELC      ansi-color.elc
  ELC      apropos.elc
  ELC      arc-mode.elc
  ELC      array.elc
  ELC      auth-source-pass.elc
  ELC      auth-source.elc
emacs: emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
emacs: Trying to load incoherent dumped eln file /build/source/native-lisp/28.0.50-x86_64-pc-linux-gnu-09601573b7325af2dd29f4a680580778/utf-8-lang-7d52ad2081cd9b7defa1e59df69cd1ee-8060c675d6ff533c8d3f4bc6c66ddee1.eln
make[2]: *** [Makefile:319: align.elc] Error 1
make[2]: *** Waiting for unfinished jobs....
make[2]: *** [Makefile:319: ansi-color.elc] Error 1
make[2]: *** [Makefile:319: apropos.elc] Error 1
make[2]: *** [Makefile:319: emacs-lisp/eieio-base.elc] Error 1
make[2]: *** [Makefile:319: allout.elc] Error 1
make[2]: *** [Makefile:319: arc-mode.elc] Error 1
make[2]: *** [Makefile:319: allout-widgets.elc] Error 1
make[2]: *** [Makefile:319: auth-source-pass.elc] Error 1
make[2]: *** [Makefile:319: cedet/semantic/db.elc] Error 1
make[2]: *** [Makefile:319: emacs-lisp/eieio.elc] Error 1
make[2]: *** [Makefile:319: array.elc] Error 1
make[2]: *** [Makefile:319: auth-source.elc] Error 1
make[2]: Leaving directory '/build/source/lisp'
make[1]: *** [Makefile:353: compile-main] Error 2
make[1]: Leaving directory '/build/source/lisp'
make: *** [Makefile:421: lisp] Error 2
builder for '/nix/store/gvdjc592l59izwviqlf9xrxhpy52rv74-emacs-gcc-20210109.0.drv' failed with exit code 2
cannot build derivation '/nix/store/c1nnd7zgi7sxfrjgayb739dfwf1gdisp-emacs-gcc-with-packages-20210109.0.drv': 1 dependencies couldn't be built
cannot build derivation '/nix/store/hhgy50ymk3003xwg3kpzwrs1qwcws5qx-emacs-gcc-with-packages-20210109.0_fish-completions.drv': 1 dependencies couldn't be built
cannot build derivation '/nix/store/fr2xyprash80m7h8bn5hwkbj5r38f0bh-man-paths.drv': 1 dependencies couldn't be built
cannot build derivation '/nix/store/gvg95x00sg1q2v4jdqps044nqyvp7sj3-system-path.drv': 1 dependencies couldn't be built
cannot build derivation '/nix/store/33ybzj1fnpspy7qlqcjaslvpy1jnf2py-xsession.drv': 1 dependencies couldn't be built
cannot build derivation '/nix/store/q3bb2f8ynak0w5ccv4q1x22gfzval28m-xsession.drv': 1 dependencies couldn't be built
cannot build derivation '/nix/store/n8ddw62kjrci2clwhn8jhy0a0x4nqzyq-nixos-system-nixos-20.09.2538.0cfd08f4881.drv': 1 dependencies couldn't be built
error: build of '/nix/store/n8ddw62kjrci2clwhn8jhy0a0x4nqzyq-nixos-system-nixos-20.09.2538.0cfd08f4881.drv' failed

And thanks for the link to your dotfile too. I think I’m a bit closer to making it work. Just now found the options available, so at least that’s a good step in the right direction!

So I spent some more time working on it and got it working with home-manager and the manual installation step. It’s essentially a mix of the home-manager approaches outlined above (thanks, people!) and the exwm-module approach. If anyone’s interested, the current code is in my dotfiles. It might still take some tweaking, but at least it works.

One part that I haven’t quite been able to figure out is that if I try and add magit as part of the nix-managed packages (using emacsWithPackages), then compilation fails. This might be because of the native-comp branch, but it only seems to be for magit. It’s not a big deal as I only have a few packages listed there, but it’s weird none the less. The error says git-commit.el:123:1: Error: Cannot find suitable directory for output in 'comp-eln-load-path'. The full error output is:

Magit build errors
In toplevel form:
git-commit.el:123:1: Error: Cannot find suitable directory for output in `comp-eln-load-path'
Done (Total of 0 files compiled, 1 failed, 2 skipped)
Debugger entered--Lisp error: (error "transient--init-suffix-key is already defined as s...")
  error("%s is already defined as something else than a gen..." transient--init-suffix-key)
  cl-generic-ensure-function(transient--init-suffix-key)
  cl-generic-define-method(transient--init-suffix-key nil ((obj transient-suffix)) nil #f(compiled-function (obj) #<bytecode -0xa7c7ae5ba74823f>))
  byte-code("\300\301\302\303\302\304%\210\300\301\302\305\306\307%\207" [cl-generic-define-method transient--init-suffix-key nil ((obj transient-suffix)) #f(compiled-function (obj) #<bytecode -0xa7c7ae5ba74823f>) ((obj transient-argument)) t #f(compiled-function (cl--cnm obj) #<bytecode -0xf13be503ea7b8c5>)] 6)
  require(transient)
  load-with-code-conversion("/nix/store/35nh5gj7ch888k6abrlvw7b4zj5x8g41-emacs-..." "/nix/store/35nh5gj7ch888k6abrlvw7b4zj5x8g41-emacs-..." nil t)
  git-commit-setup-check-buffer()
  run-hooks(find-file-hook)
  after-find-file(nil t)
  find-file-noselect-1(#<buffer git-commit-20210102.1242.el> "/build/packages/git-commit-20210102.1242.el" nil nil "/build/packages/git-commit-20210102.1242.el" (135445 44))
  find-file-noselect("/build/packages/git-commit-20210102.1242.el")
  command-line-1(("-l" "/nix/store/4jj63z4v1xp13rh2md053dccq920hd45-elpa2n..." "-f" "elpa2nix-install-package" "/build/packages/git-commit-20210102.1242.el" "/nix/store/35nh5gj7ch888k6abrlvw7b4zj5x8g41-emacs-..."))
  command-line()
  normal-top-level()