Emacs Lisp: how to check if we haven't yet created any windows in daemon mode

On my system, Emacs segfaults if this is run in a daemon before a window is created:

(set-fontset-font (face-attribute 'default :fontset)
                  'han "Noto Sans Mono CJK JP")

I don't really know how this works, but I just need to prevent it from running during that time. So how do we check if Emacs is running as a daemon but a window hasn't been created?

(I'll go back to calling them “frames” in the rest of this article.)

TL;DR: (terminal-name) is always "initial_terminal" in a daemon before a frame is created. Like this:

/20220118T062329+0900.png

Testing

Let's launch a daemon to test with. In one terminal:

emacs -Q --fg-daemon

We use emacs -Q to not load any extra init files.

Now let's try the obvious. The Emacs manual implies that there will be no existing frame when Emacs is started as a daemon:

If the Emacs process has no existing frame—which can happen if it was started as a daemon (see Emacs Server)—then Emacs opens a frame on the terminal in which you called emacsclient .

emacsclient --eval "(frame-list)"
(#<frame F1 0x5562c8db0ea0>)

Uhh, what?

emacsclient --eval "(selected-frame)"
#<frame F1 0x5562c8db0ea0>

This frame is even selected. (frame-live-p) on it returns t, which means it claims to be displayed on a text based terminal.

Let's try this instead:

emacsclient --eval "(frame-terminal (car (frame-list)))"
#<terminal 0 on initial_terminal>

Now we're getting somewhere. We can use terminal-name to extract the initial_terminal name as a string:

emacsclient --eval "(terminal-name (frame-terminal (car (frame-list))))"
initial_terminal

And, in fact, because this frame is selected already and terminal-name automatically uses the selected frame's terminal, we can omit its argument:

emacsclient --eval "(terminal-name)"
initial_terminal

In a normal frame, this will return something else:

(terminal-name)
:0

Additional reading

After finding out the term initial_terminal it became easier to search for the same answer.

  • Why does (frame-list) return one extra frame in daemon mode?

    It's a "physically invisible" frame (even though frame-visible-p says otherwise) associated with initial terminal where the daemon was started. I suspect that a sole reason for its existence is that emacs is not ready to run with no frames at all, and it's hard enough to fix it.

  • Deferring interactive prompts during startup in daemon mode.

    This uses

    (string-equal (terminal-name (get-device-terminal nil))
                  "initial_terminal")

    to determine the same thing: whether we're in daemon mode and before any frames have been created. (get-device-terminal nil) returns the display terminal of the selected frame, so (terminal-name (get-device-terminal nil)) is exactly the same as (terminal-name).

  • This technique is in fact used in Emacs itself, in debug.el. This was added in Emacs 27, in this commit.

    /20220118T062329+0900.png

  • What is the difference between a terminal and a frame? Terminals are basically output devices. Multiple frames can share the same terminal.

  • The “initial terminal” in its current form was added in this commit, released in Emacs 23.