back to homepage

Eliminating Terminal Startup Latency

My workflow revolves around the terminal. For example, to launch an application such as Chromium or Krita, I press the keybind Win+space to open a new floating Alacritty terminal in the center of my screen. Then, I type krita or the name of whatever application to launch it. I have tons of aliases in my .bashrc that look like this to close the terminal when I open an application:

alias firefox='firefox & disown; exit'
alias krita='krita & disown; exit'
alias blender='blender & disown; exit'
alias chromium='chromium & disown; exit'

I felt a noticeable but subtle startup latency between when I press the keybind Win+space and when the terminal actually appears on screen. To take a closer look at this issue, ffmpeg captured a screen recording for me. Looking over the footage, I noticed a few frames of latency (>100ms) between when my keypress was detected and when the terminal appeared on screen.

I fixed this latency problem by baking a solution into DWM, my window manager. Instead of attempting to optimize how quickly Alacritty can load, my window manager "pre-saves" a terminal. It spawns an Alacritty process and keeps note of its PID. Then, it "catches" this Alacritty window: If a request comes to manage a window with the same PID as the recently opened Alacritty instance, we save the window data into a global struct instead of managing the window. By not mapping the window, which is one thing the manage function does, the window is fully loaded but does not appear as a Xorg window.

Next time the user presses the keybind Win+space, the window manager uses the saved window data to manage the saved terminal, which occurs with zero frames of latency. Following this, the window manager performs the aforementioned procedure of saving another terminal for the next time the user wants one. Let's see how I implemented this feature in detail. In the codebase of the window manager, which exists solely in one minimalistic two thousand line C file, we define this in the global scope:

struct SavedWindow {
    Window win; XWindowAttributes *attr;
};
struct SavedWindow savedterm = {.win = 0};
int pid_of_window_to_catch = -1;

Once an Alacritty process is spawned, the PID of that process is stored into the pid_of_window_to_catch variable. This allows us to intercept the process in the manage function, to prevent it the window from being mapped.

I created a function to launch a terminal, which I map to the keybind Win+space in my config. If the saved terminal's window exists, e.g. its ID is not 0, then we manage the saved terminal's window and in effect use it. In the edgecase that the window was somehow managed before we could use it, and that no catched window exists, we just catch another terminal.

void
launch_terminal(const Arg *a) {
    if (savedterm.win != 0) {
        manage(savedterm.win, savedterm.attr); // use saved terminal
    }

    if (savedterm.win == 0 && pid_of_window_to_catch != -1) { // edgecase
        pid_of_window_to_catch = -1;
    }

    // spawn new terminal for saving
    if (pid_of_window_to_catch == -1) {
        Arg arg = {.v = (const char *[]){"alacritty", NULL}};
        pid_of_window_to_catch = spawn_(&arg);
    }
}

Then, in the function where windows are managed, I prepended some code to match against the Alacritty process which we should intercept and prevent from being managed. We copy the window and window attributes so we can restore window by managing it at a later date.

void
manage(Window w, XWindowAttributes *wa)
{
    if (w != savedterm.win && pid_of_window_to_catch == winpid(w)) {
        pid_of_window_to_catch = -1;
        if (savedterm.win != 0) {
            free(savedterm.attr);
        }
        XWindowAttributes *wa_copy = malloc(sizeof(XWindowAttributes));
        memcpy(wa_copy, wa, sizeof(XWindowAttributes));
        savedterm = (struct SavedWindow){w, wa_copy};
        return;
    }
    if (w == savedterm.win) {
        savedterm.win = 0;
    }

    ...
}

Really, my solution is very similar to Tavis Ormandy's solution. He accomplishes it by intercepting the X map requests from the terminal application itself, which is probably more robust than my solution. Launching a terminal, noting its PID, and catching it feels like a more fragile process. But it has worked remarkably well so far.