v0.17.0 Faster text rendering: printed text to cells in C

Kill it. Crash it.
Your tabs are still there.

guiltty is a Linux-only, GPU-accelerated terminal emulator written in Elixir and C. Every tab is a supervised process tree and the renderer is its own OS process, so when a part crashes, its supervisor restarts it and your tabs stay.

you@linux:~/src
╭─ │ ~/src ✔ │ at 16:24:55 ─╮
╰─
milestones implemented
16
milestones implemented
renderers, the same pixels
2
renderers, the same pixels
rows of scrollback
10,000
rows of scrollback
book chapters on how it works
26
book chapters on how it works

How it works

Elixir does the thinking. C draws the pixels.

Your shell writes bytes into a pseudoterminal. A NIF hands them to the BEAM, where a parser and a screen, plain Elixir processes, turn them into cells. The renderer is a separate program that only draws: if it dies, guiltty starts a new one and your tabs are still there.

Your shell

bash, zsh, fish: your login shell

the BEAM

PTY NIF

C · enif_select

Parser

DEC state machine

Screen

grid, scrollback

Renderer

its own process · Vulkan or GL

Features

A daily driver, with the BEAM underneath.

The things you expect from a modern terminal, and a few that come almost for free with a supervision tree.

Vulkan 1.3, or OpenGL. Same pixels.

guiltty draws with Vulkan by default. No driver? It falls back to OpenGL by itself and says so in the tab bar, and GUILTTY_RENDERER=gl picks one for a run. A test replays recorded frames through both and checks they match, pixel for pixel.

Lockable tabs

Lock a tab and close its window: it keeps running, and the next guiltty brings that window back with it.

Ctrl + Shift + L

Safe broadcast

Type once into every tab in the group. If a connection drops, typing goes nowhere, so a command meant for your servers doesn't land on your own machine.

Ctrl + Shift + B

Unicode, properly

Grapheme clusters, wide characters, HarfBuzz shaping, ligatures, fallback fonts, color emoji and Nerd Font icons that spill into the next cell.

Live config, in Elixir

config.exs is plain Elixir, applied to the open window on save. Mistakes show in red in the tab bar, with a suggestion for misspelled settings.

Full-screen programs

Alternate screen, scroll regions, mouse reporting, bracketed paste, cursor shapes. Tested with vim, less, htop, btop and tmux.

Many windows, one guiltty

Each window has its own tabs, locked tabs and broadcast group. Closing one never touches the others; guiltty stops after the last.

Ctrl + Shift + N

A window in 0.1 s

guiltty is a small client that asks the running guiltty for a window over a private socket, like emacsclient. About 0.1 s with guiltty running, 0.7 s from cold, on the developer's machine.

Measured, then faster

Floods of short lines (seq, logs) land about 3x faster than in v0.13, laid down together and scrolled once. make bench measures it.

Scrollback, search, selection

10,000 rows per tab, searched with Enter and Shift+Enter. Select to copy to the primary selection, middle click to paste. Backpressure from the shell to the screen means a flood of output can't outrun the screen.

search
[info] worker 3 started
[info] request GET /health 200 in 1ms
[info] worker 4 started
[info] request GET /health 200 in 2ms

Self-healing

Go on. Kill something.

A simulation of guiltty's real supervision tree. Click a process to kill it and watch what its supervisor restarts. In the real thing, you'd barely notice.

  • Each tab is its own rest_for_one tree: Pty, Screen, Parser.
  • Every window is its own Desk: crash one and the others never notice.
  • kill -9 the renderer and its Desk starts a new Window. The screen heals.

Processes killed: 0

iex(guiltty@127.0.0.1)1> :observer
  • Guiltty.Supervisor <0.212.0>
  • Guiltty.Registry <0.219.0>
  • Guiltty.Config <0.226.0>
  • Guiltty.Sessions <0.233.0>
  • <0.240.0>
  • <0.247.0>
  • <0.254.0>
  • <0.261.0>
  • Session.Supervisor 2 <0.268.0>
  • Guiltty.Desks <0.275.0>
  • <0.282.0>
  • <0.289.0>
  • Guiltty.Window <0.296.0>
  • pid 9393
  • Desk 2 <0.310.0>
  • Guiltty.Lifeline <0.317.0>
log

Themes

Built-in themes. Or bring your own.

Pick one by name, or start from one and change single colors. Every terminal on this page follows your pick.

Tomorrow Night is what guiltty uses without a config.

~/.config/guiltty/config.exs
# the last expression is the config
[
  font: [family: "JetBrainsMono Nerd Font", size: 14],
  ligatures: true,
  theme: "tomorrow-nightcatppuccin-mochadraculagruvbox-darksolarized-dark",
  cursor: [style: :block, blink: false],
  renderer: :vulkan
]

Keys

Rebind any of them.

The defaults, as they ship; paste (Ctrl+Shift+V) is the one fixed key. Forgot one? Ctrl + Shift + / opens the cheatsheet, built from your own config.

New window Ctrl + Shift + N
New tab Ctrl + Shift + T
Close tab (asks if busy) Ctrl + Shift + W
Previous tab Ctrl + PgUp
Next tab Ctrl + PgDn
Move tab left Ctrl + Shift + PgUp
Move tab right Ctrl + Shift + PgDn
Rename the tab Ctrl + Shift + R
Scroll back Shift + PgUp
Clear the scrollback Ctrl + Shift + K
Search the scrollback Ctrl + Shift + F
Copy Ctrl + Shift + C
Paste Ctrl + Shift + V
Zoom in Ctrl + =
Zoom out Ctrl + -
Reset zoom Ctrl + 0
Open the config file Ctrl + ,
The cheatsheet: every key Ctrl + Shift + /
Lock the tab 🔒 Ctrl + Shift + L
Broadcast tabs Ctrl + Shift + B

Install

Built on your machine. Runs without Elixir.

guiltty builds from source, so you need Elixir 1.18+ on Erlang/OTP 26+, and GCC 15+, once. The installed copy carries its own Erlang runtime and needs no root.

1 What to install first

shell
sudo zypper install gcc make pkg-config SDL3-devel freetype2-devel \
  fontconfig-devel harfbuzz-devel vulkan-devel shaderc elixir erlang-epmd

The one guiltty is developed on.

Only openSUSE has been tried; the other package names come from each distribution's package listings.

Tested Elixir and Erlang/OTP

The minimum is Elixir 1.18 on Erlang/OTP 26. Older versions aren't supported.

Elixir Erlang/OTP Status
1.20.4 29 Developed on
1.19.7 28 Builds, tests pass
1.19.7 26 Builds, tests pass
1.18.4 26 Builds, tests pass

2 Clone it and run the installer

shell
git clone https://github.com/z7ealth/guiltty.git
cd guiltty
./install.sh
Update
git pull && ./install.sh
Uninstall
./install.sh --uninstall
Elsewhere
PREFIX=/opt/guiltty ./install.sh

3 Run it

Type guiltty or find it in your applications menu. It needs a GPU with OpenGL 3.3, and draws with Vulkan where there's a 1.3 driver. It looks best with a Nerd Font, Noto Color Emoji and a font for Chinese, Japanese and Korean.

Read the docs: keys, settings, themes

The build

Implemented in milestones.

Each version is one milestone of the build plan, from the first byte through a PTY to tab comforts. The devlog tells how each one went.

  1. 0.1

    First light

    Oct 29

    The whole pipeline: a PTY NIF, a parser, the screen in Elixir and an OpenGL renderer. ASCII only, 80×24.

  2. 0.2

    Color

    Oct 29

    16, 256 and 24-bit color, Paul Williams' DEC parser, and a resizable window.

  3. 0.3

    The vim fight

    Oct 29

    Alternate screen, scroll regions, mouse reporting. vim, less, htop, btop and tmux work.

  4. 0.4

    The BEAM advantage

    Oct 29

    Tabs, self-healing sessions, detach and reattach, live config, backpressure.

  5. 0.5

    Daily driver

    Oct 30

    Unicode, HarfBuzz shaping, color emoji, 10,000 rows of scrollback, search, HiDPI.

  6. 0.6

    Flat tabs

    Oct 30

    The flat tab bar, named themes, cursor and window settings, the cheatsheet.

  7. 0.7

    The installer

    Oct 30

    ./install.sh, a menu entry and the logo.

  8. 0.8

    Lockable tabs

    Oct 30

    Lock a tab and it outlives the window.

  9. 0.9

    Broadcast

    Oct 30

    Type once into every tab in the group, without typing into your own machine by mistake.

  10. 0.10

    Vulkan

    Oct 30

    A Vulkan 1.3 renderer, pixel-for-pixel the same as OpenGL, with automatic fallback.

  11. 0.11

    Ligatures

    Oct 30

    With a font that has them, |>, ->, != and === are drawn as one symbol. Cleaner tabs, too.

  12. 0.12.4

    Security

    Oct 30

    Hardened C, a localhost-only node with a private cookie, a pidfd for the shell, and titles that can't fake a locked tab.

  13. 0.13

    Multi window

    Oct 30

    Several windows, each with its own tabs, locked tabs and broadcast group. Ctrl+Shift+N opens one.

  14. 0.14

    Performance

    Oct 31

    Floods of short lines show up 3x faster, startup is 100 ms sooner, and make bench measures it all.

  15. 0.15.3

    Client and server

    Oct 31

    guiltty is a small client that asks the running guiltty for a window, like emacsclient: 0.1 s.

  16. 0.16.1

    Tab comforts and styles

    Nov 1

    Busy tabs ask before closing, ● and 🔔 mark tabs you're not looking at, tabs can be renamed and reordered, and drawn flat, powerline, rounded, minimal or with separators.

Go on, try to break it.

Linux. Tried on openSUSE Tumbleweed, X11 and an AMD GPU; Wayland, Intel and NVIDIA should work but haven't been tried. Open source, MIT licensed.