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:
- Mouse support in terminal Emacs.
- Ctrl+Backspace — and other previously-impossible key combos — via the kitty keyboard protocol.
- 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] osc52infoot.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.)