Migrating my remote Emacs from alacritty+mosh to foot+et

I run Emacs on a handful of different servers and connect to it from my laptop. In this article the server is genos and the laptop is framework, talking to each other over Tailscale. For years my remote setup has been:

  • alacritty — the terminal emulator
  • mosh — a resilient remote shell
  • emacsclient — to attach to the remote Emacs

(I also use graphical remote sessions like turbovnc and x2go, but those are a separate story; this post is only about the terminal stack.)

The terminal path is nice for being low-latency and low-bandwidth, but mosh has a couple of long-standing annoyances:

  • It reimplements a terminal server-side, so it mangles or drops escape sequences it doesn't understand. That quietly blocks modern terminal features.
  • Orphaned sessions. The session key lives only in the mosh client's memory, so if you reboot the client the server-side mosh-server is stranded and unreachable — you have to ssh in and kill it by hand. I'd managed to pile up five of them when I did this migration.

So I asked myself: maybe something has happened in this space over the last ten years? Who knows. So I'm now trying a different stack:

  • foot — a Wayland-native terminal
  • et — Eternal Terminal, a resilient remote shell over TCP that reconnects
  • emacsclient — unchanged

What I gained

Three things that never worked over alacritty+mosh now work in remote terminal Emacs, bringing it to near-parity with GUI Emacs:

  1. Mouse support in terminal Emacs.
  2. Ctrl+Backspace — and other previously-impossible key combos — via the kitty keyboard protocol.
  3. M-w copies to the laptop's system clipboard (OSC 52), so I can paste text from the remote Emacs straight into Firefox on the laptop.

The rest of this post is how each one came together, plus one good gotcha.

The kitty keyboard protocol (keys like Ctrl+Backspace)

Terminals historically can't send a distinct byte sequence for Ctrl+Backspace versus plain Backspace, so Emacs never sees a distinct event. The modern fix is the kitty keyboard protocol, which foot (and kitty) support but alacritty does not(AFAIK anyway). Emacs needs the kkp package to negotiate it:

1(use-package kkp
2  :ensure t
3  :config (global-kkp-mode +1))

global-kkp-mode is a no-op on terminals or GUI frames that don't speak the protocol, so it's safe to enable everywhere. It works over plain ssh and over et, because both forward the negotiation sequences. (alacritty users are stuck doing per-key escape-sequence hacks in alacritty.toml instead. I initially tried this, but it seemed like reinwenting wheels)

Mouse in terminal Emacs

This one turned out to be simple, and was never about the terminal at all — xterm-mouse-mode is just off by default:

1(unless (display-graphic-p)
2  (xterm-mouse-mode 1))
3;; and for emacsclient -t / daemon tty frames:
4(add-hook 'after-make-frame-functions
5          (lambda (frame)
6            (unless (display-graphic-p frame)
7              (with-selected-frame frame (xterm-mouse-mode 1)))))

The hook matters because daemon and emacsclient -t frames are created after init, so you have to switch the mode on as each tty frame appears.

So this should have worked in alacritty, I just never tried.

M-w to the laptop clipboard (OSC 52)

Copy-paste from alacritty works, but it isn't integrated with Emacs: you mouse-select something, hit Ctrl+C to copy and Ctrl+V to paste, all outside the editor's own kill-ring. I wanted M-w in the remote Emacs to land on the laptop's clipboard.

OSC 52 is a terminal escape sequence that means "put this text on the host clipboard". The raw form looks like:

1printf '\033]52;c;%s\007' "$(printf 'hello' | base64)"

Run that in foot and the system clipboard becomes "hello". The payload is base64 so arbitrary text can't break the sequence; c selects the clipboard; \033 is ESC and \007 is BEL, the terminator.

I debugged the clipboard chain by testing each layer in isolation with that printf and checking the result with wl-paste:

  • foot accepts OSC 52 — it's enabled by default; see [security] osc52 in foot.ini. PASS.
  • et forwards OSC 52 down to the laptop's foot. PASS. (I'd wrongly assumed et would swallow it the way mosh does — it doesn't.)
  • Emacs was the missing link. The remote terminal Emacs simply wasn't emitting OSC 52 on M-w. Emacs's built-in OSC 52 support gates on a terminal-capability auto-detection that doesn't reliably fire over a remote connection.

The fix is the clipetty package, which emits OSC 52 unconditionally on every kill:

1(use-package clipetty
2  :ensure t
3  :hook (after-init . global-clipetty-mode))

After that, M-w in the remote Emacs lands in the laptop's clipboard. clipetty is essentially "run that printf for me automatically on every copy".

A gotcha worth a paragraph: terminfo after a server upgrade

Mid-migration the et terminal suddenly broke — garbled cursor, wrong colors, mangled keys. The cause: I'd upgraded the server, and in the process it lost the foot terminfo entry. foot on the laptop advertises TERM=foot (or foot-extra), and programs on the server look that name up in their own terminfo database to learn what the terminal can do. If the server doesn't have it, everything renders wrong.

Note this is not about installing the foot program on the server — only its terminfo description:

1sudo dnf install foot-terminfo

This is a general remote-terminal gotcha — kitty, alacritty and wezterm all have the same issue: your terminal's terminfo has to exist on every host you run interactive programs on. Installing the explicit foot-terminfo package also makes it robust against future upgrades, rather than relying on whatever happens to be bundled in ncurses-base.

et vs mosh

I haven't run et long enough to give an honest verdict yet, but here are some first impressions:

  • et fixes the orphan problem: it re-bootstraps auth through ssh on reconnect, so a client reboot doesn't strand an unreachable session.
  • et is a more transparent pipe than mosh, so kkp and OSC 52 pass straight through (mosh blocks both).
  • The tradeoff: et has no local-echo prediction like mosh, so on a very high-latency link mosh feels snappier. On a stable Tailscale link between my own machines, that's irrelevant.
  • For the truly-resilient "survive a client reboot with zero orphans" ideal, the cleanest model is to keep the durable session as an Emacs daemon on the server and treat the transport (ssh or et) as disposable.
  • One open question: Ctrl+C doesn't seem to behave the way it used to. I haven't pinned down whether that's et's signal handling or a kkp interaction yet — still investigating.

Summary config (all in init.el)

1(use-package kkp :ensure t :config (global-kkp-mode +1))
2(unless (display-graphic-p) (xterm-mouse-mode 1))
3(use-package clipetty :ensure t :hook (after-init . global-clipetty-mode))

Plus sudo dnf install foot-terminfo on the server. That's the whole delta to get remote terminal Emacs feeling like local GUI Emacs.

(Verified on framework as client and genos as server, both Fedora 44, with foot 1.27 and et 6.2.11.)