Angels and daemons — a tale of emojis in emacs
Table of Contents
I have been a GNU Emacs user for many years and it has always served me well. It is lightweight, interacts with all the tooling I use and its customizability has made it possible to “make it mine”. Nevertheless, it is a big piece of software and I don‘t pretend to understand it fully. As a result, ever so often, something breaks and I have no idea why. Today was such a day.
Today, whenever I tried to open a particular Readme.md
, Emacs would crash. No errors, no nothing. Just the default macOS crash dialog. Upon opening this Readme.md
in the browser using grip
I noticed that this file had a few emojis in there. Now, Emacs is a very old project which means a feature such as emoji support is not straightforward to add as it touches the core font rendering engine. So, knowing that my version of Emacs (26) was not the best at supporting multicolor fonts (such as Apple Color Emoji
), I decided to upgrade to version 27.1 which had added support for multicolor font rendering (“Multicolor fonts such as "Noto Color Emoji" can be displayed on Emacs configured with Cairo drawing and linked with cairo >= 1.16.0.”). I hoped that an upgrade would solve all my issues and I could go back to working on my project. Nope.
After upgrading to GNU Emacs 27.1
, I tried to reopen the Readme.md
from earlier and 💥 Emacs crashed yet again! It was time to go down that rabbithole..
Root cause analysis
Problem statement
Attempt to render an emoji causes Emacs to crash.
I was able to reproduce it by creating a text file with just one 🤷🏼♂️ in it and then trying to open it in a Emacs instance (init file load disabled). This Emacs instance was created by emacsclient
which is my preferred way of using Emacs as I run a local Emacs server.
Environment
- Latest macOS Catalina
- GNU Emacs 27.1 built with multicolor font rendering support and no init file
- GNU Emacs running in daemon mode or as a server
- Default unicode font set in Emacs set to “Apple Color Emoji”
(set-fontset-font "fontset-default" 'symbol "Apple Color Emoji" nil 'prepend)
Hypothesis
Running Emacs as a server (emacs --daemon
) was causing some funny business with my font configuration. To verify this, I first ran Emacs.app standalone from /Applications
and tried to open Readme.md
and lo' and behold, it worked! I could see emojis and there were no more crashes!
Now most would stop here but this was quite cumbersome for me as I prefer running Emacs as a server and connecting to via emacsclient
. Why? So that I do not incur the cost of initializing an instance + all my customizations every time I open a new window (frame in Emacs parlance 🤦🏼♂️). Therefore, I dug deeper.
Next, from my running standalone Emacs instance, I started a server (M-x start-server
), connected to it via emacsclient
and tried to open Readme.md
. To my surprise, the file was opened successfully and the emacsclient
instance did not crash. Nor did the server.
Huh? 🤨
Yeah, me too.
This reminded me of an issue I had run into long ago where themes would refuse to get applied correctly to an Emacs instance spawned by emacsclient
connecting to a running Emacs server. It had something to do with the server not creating a frame upon start up. I went hunting and found this snippet of text on the Emacs startup summary page
If appropriate, it creates a graphical frame. As part of creating the graphical frame, it initializes the window system specified by initial-frame-alist and default-frame-alist (see Initial Parameters) for the graphical frame, by calling the window-system-initialization function for that window system. This is not done in batch (noninteractive) or daemon mode.
This is not done in batch (noninteractive) or daemon mode
I guess since there was no frame created the font config was not loaded as nothing would use that config. Armed with this knowledge and my lackluster emacs lisp skills, I had a solution in mind to implement!
Solution
As an aside, one way Emacs can be extended is by attaching functions to "hooks". This is quite like event handlers.
I was aware of the after-make-frame-functions
hook that runs a function after creating a frame. Also, from my past run in with a similar issue, I knew I only had to apply the config once and Emacs would retain the config for all new frames created. So to have things work, I had to instruct Emacs to apply the font config after creating a frame (triggered by a client connecting to the Emacs server) and then have it remove the handler for the after-make-frame-functions
hook so that the config was only applied once. Also, I had to make sure we did this only if Emacs was running as a server i.e., in daemon
mode.
The solution I hacked together is what you see below (annotated with comments). It works as expected and I now have emojis in my editor irrespective of whether I use Emacs in server mode or in standalone mode! 😇
;; Add Apple Color Emoji to the default symbol fontset used by Emacs
(defun zdx/set-emoji-font ()
(set-fontset-font "fontset-default" 'symbol "Apple Color Emoji" nil 'prepend))
;; Call the config function once and then remove the handler
(defun zdx/set-emoji-font-in-frame (frame)
(with-selected-frame frame
(zdx/set-emoji-font))
;; Unregister this hook once its run
(remove-hook 'after-make-frame-functions
'zdx/set-emoji-font-in-frame))
;; Attach the function to the hook only if in Emacs server
;; otherwise just call the config function directly
(if (daemonp)
(add-hook 'after-make-frame-functions
'zdx/set-emoji-font-in-frame)
(zdx/set-emoji-font))
And there you have it! Another day, another new thing learnt!