Keep Emacs alive through X crashes by running it in the background with –daemon

Posted: - Modified: | emacs

I periodically have to kill and restart my X server when it freezes. I probably have the wrong configuration, since it starts off in low-graphics mode until I manually restart the graphical login manager (I'm using sddm). Anyway, since it's been hard to debug and fix that issue, I figured I'd address the part that really bugs me when I restart X: dealing with ungraceful Emacs exits. M-x recover-session does a decent job of restoring my modified files and I usually remember to call it from the scratch screen, but sometimes I forget, and then I end up losing changes.

I started taking advantage of the fact that I used (server-start) to enable other Emacs clients to connect to the same process. Whenever I needed to restart X, I'd first switch to a console, use emacsclient -c to connect to Emacs, and use M-x save-buffers-kill-emacs to close my Emacs neatly. Still, starting my Emacs from X meant that I had to restart Emacs each time I restarted X.

It turns out that you can run Emacs as a background process with emacs --daemon and then connect to it with emacsclient -c. When I did that, though, the emacsclient frame didn't have my color theme applied. This after-make-frame-functions addition fixes that:

(defun my/setup-color-theme ()
  (interactive)
  (color-theme-solarized-dark)
  (set-face-foreground 'secondary-selection "darkblue")
  (set-face-background 'secondary-selection "lightblue")
  (set-face-background 'font-lock-doc-face "black")
  (set-face-foreground 'font-lock-doc-face "wheat")
  (set-face-background 'font-lock-string-face "black")
  (set-face-foreground 'org-todo "green")
  (set-face-background 'org-todo "black"))
(add-hook 'after-make-frame-functions
          (lambda (frame)
            (select-frame frame)
            (my/setup-color-theme)))

Once I got emacs --daemon and emacsclient -c working to my satisfaction, I decided to go one step further and get it to run automatically when I start my computer. I tried the init script at https://www.emacswiki.org/emacs/EmacsAsDaemon . My Linux install uses systemd, so enabling the init script resulted in a message about a missing service file. I removed the init script and created this ~/.config/systemd/user/emacs.service instead:

[Unit]
Description=Emacs: the extensible, self-documenting text editor

[Service]
Type=forking
ExecStart=/usr/local/bin/emacs --daemon
ExecStop=/usr/local/bin/emacsclient --eval "(progn (setq kill-emacs-hook 'nil) (kill-emacs))"
Restart=always
TimeoutStartSec=0

[Install]
WantedBy=default.target

and then I ran these commands as my regular user account:

systemctl --user enable emacs
systemctl --user start emacs

Then I changed the emacs in my ~/.xsession to emacsclient -c, so that a new X session would connect to the existing session instead of starting a new one. That way, Emacs automatically started as my user whenever I restarted the computer, and I connected to that process when I started X.

Still, restarting X caused the Emacs daemon to crash. This is a GTK-related bug which emacs --daemon warns you about. I recompiled Emacs using ./configure --with-x-toolkit=lucid; make; make install. It seems to work fine now; I can ungracefully restart X, and my Emacs stays the same. Bonus: because Emacs gets initialized when I start my computer and all I need to do is connect to that process, when I log in, it feels like Emacs starts up really quickly.

Final touches: I noticed that TRAMP couldn't find my SSH keyring, so it got stuck waiting for my passphrase when I tried running remote scripts in Org Babel like so:

#+begin_src sh :dir /sacha@direct.sachachua.com:~
perl library-new.pl Business
#+end_src

Because my SSH socket looks like /tmp/ssh-BLAHBLAHBLAH/agent.PROCESSID, the SSH_AUTH_SOCK setting (Environment=SSH_AUTH_SOCK=%t/keyring/ssh) described on EmacsWiki didn't work for me. This snippet from https://github.com/nhoffman/.emacs.d/blob/master/init.org worked, though.

(defun my/ssh-refresh ()
  "Reset the environment variable SSH_AUTH_SOCK"
  (interactive)
  (let (ssh-auth-sock-old (getenv "SSH_AUTH_SOCK"))
    (setenv "SSH_AUTH_SOCK"
            (car (split-string
                  (shell-command-to-string
                   "ls -t $(find /tmp/ssh-* -user $USER -name 'agent.*' 2> /dev/null)"))))
    (message
     (format "SSH_AUTH_SOCK %s --> %s"
             ssh-auth-sock-old (getenv "SSH_AUTH_SOCK")))))
(my/ssh-refresh)

Also, emacs --daemon didn't pick up the default browser I'd configured in KDE. Opening URL links from Org Mode started a separate Chromium process instead of using Google Chrome. I fixed that by switching from browse-url-default-browser to browse-url-generic, like so:

(setq browse-url-generic-program "google-chrome")
(setq browse-url-browser-function 'browse-url-generic)

Let's see how this works!

You can comment with Disqus or you can e-mail me at sacha@sachachua.com.