Index Blogs

Emacs Daemon macOS

Published on 19 Sep 2019.

I've wanted to do this for a while but have only just done so recently as I previously would just use M-x server-start every time I started Emacs instead of actually just spending 5 minutes to setup the daemon. Anyway here's how to set it up:

First you need to add a plist file at ~/Library/LaunchAgents/org.gnu.emacs.plist which holds the definition for launchd(8) to start monitor the daemon service. We are using the --fg-daemon argument here instead of --daemon because launchd requires the process not to fork() in order to keep track of it as a child process.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.gnu.emacs</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Applications/Emacs.app/Contents/MacOS/Emacs</string>
        <string>--fg-daemon</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Then run launchctl load -w ~/Library/LaunchAgents/org.gnu.emacs.plist to load the service. After you've done this once you can just use the launchctl maintenance commands instead:

launchctl start org.gnu.emacs
launchctl stop org.gnu.emacs

Though you should be careful since stop will just kill the process and you might lose any unsaved data.

I then put an alias in my ~/.zshrc (but you can adjust this for whichever shell you are using).

alias emacsclient="/Applications/Emacs.app/Contents/MacOS/bin/emacsclient"
alias et="emacsclient -tc"
export ec(){
    emacsclient -n -e "(if (> (length (frame-list)) 1) 't)" | grep -q t
    if [ "$?" = "1" ]; then
        emacsclient -c -n $@
    elif [ $# -eq 0 ]; then
        emacsclient -ne "(select-frame-set-input-focus (window-frame (selected-window)))" >/dev/null
    else
        emacsclient -n $@
    fi
}

The first two are pretty self explanatory, and ec creates a frame if one doesn't already exist, will make the frame active if one already exists but you don't open another buffer, or will open another buffer in the existing frame.

You may need to adjust your configuration for loading stuff like themes and GUI related things so that they work correctly when starting the Emacs daemon. I use the following snippet to make sure that my them always gets loaded correctly:

  (if (daemonp)
    (add-hook 'after-make-frame-functions
      (lambda (frame) (with-selected-frame frame (load-theme 'spacemacs-dark t))))
    (load-theme 'spacemacs-dark t))