- Zig 91.5%
- Nix 8.1%
- GLSL 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks are pending
test / test (push) Has started running
A window now holds tabs: Tab.zig takes the session and what the window kept about it (render state, selection, title, hold) out of Window, which keeps the platform window, the renderer and the list. Only the shown tab is drawn, but every tab's session is polled and resized with the window. decor/TabBar.zig draws the strip of tabs under the title bar, shown once there are two (window.tab_bar = auto | always | never): a press shows a tab, the middle button or its x closes it, + or a double-click on the empty bar opens one. Key bindings are GNOME Terminal's: ctrl+shift+t and ctrl+shift+w, ctrl+page_up/page_down and ctrl+tab, ctrl+shift+page_up/ page_down to move, alt+1..9; close_window moves to ctrl+shift+q. A new tab starts in the shown tab's directory, from OSC 7 or /proc/PID/cwd. `harrier --tab` and the new-tab action put a tab in the window last opened or focused, raising it with the activation token. LaunchCommand now opens one window with a tab per command, as the Terminal Intent suggests. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KaBaVYY8vv3qA6sgbQBuZT |
||
| .forgejo/workflows | ||
| dbus | ||
| dist | ||
| LICENSES | ||
| nix | ||
| shaders | ||
| src | ||
| tests/nixos | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| build.zig.zon.nix | ||
| flake.lock | ||
| flake.nix | ||
| package.nix | ||
| README.md | ||
| REUSE.toml | ||
harrier
A GPU terminal emulator in Zig 0.17. The terminal itself — parsing, the screen, scrollback, modes, key and mouse encoding — is libghostty-vt. harrier is everything around it: a Wayland window, a Vulkan renderer, fonts, and a program on a pseudo-terminal or a serial port at the other end.
$ harrier # your shell
$ harrier -e htop # a program
$ harrier --serial /dev/ttyUSB0 --baud 9600 # a serial port
One harrier serves every window. A launch with a harrier already running hands its window to that one and exits, so a new window opens at once. Where the systemd user service is installed, the session bus starts harrier for the first window too; see Running as a service.
It is new, and runs on Linux under Wayland. The seams for a Windows port are in place (see Design), and nothing behind them exists yet.
What it does
- The terminal is Ghostty's. It uses libghostty-vt from Ghostty's
zig-0.17branch (pull request #14519): xterm and kitty keyboard and mouse encoding, synchronized output, OSC 52 clipboard writes, titles, scrollback, and the rest of what Ghostty's own terminal does. - A pty or a serial port. The window shows whatever the backend is
connected to. A program runs on a pseudo-terminal with
TERM,COLORTERM=truecolorandTERM_PROGRAM=harrierset. A serial port is opened raw through zig-serial, at any rate the driver accepts. - Wayland, without libwayland. The window and its input go through
zig-wayland-native and zig-xkb:
- fractional scaling;
- server-side decorations;
- key repeat;
- dead keys and the Compose key;
- the clipboard, and the primary selection: select to copy, middle-click to paste;
- input methods through text-input-v3;
- cursor shapes;
- its own title bar and frame where the compositor draws none.
- Vulkan, through dma-bufs. Every frame is instanced quads drawn from a
glyph atlas. There is no
wl_displayfor a Vulkan swapchain, so harrier does what a swapchain does itself: it allocates exportable images the compositor reads directly, in a layout (DRM format modifier) both sides share. The frame is drawn straight into one of these.- Without modifier support (RADV on Polaris and older, say), there is no image both can use. The buffer is then a linear dma-buf, and the frame is copied into it on the GPU.
- Sync is explicit (linux-drm-syncobj) where the compositor offers it, and implicit otherwise.
- Where no memory can be shared at all, the frame is read back into shared memory.
- Jeff's font stack.
-
Fonts are found the way fontconfig finds them, by zig-font-config.
-
Text is shaped by zig-font-shaper, a port of HarfBuzz, so ligatures and combining marks work. Only rows that changed are shaped again.
-
Glyphs are drawn by zig-font-renderer, including
COLRcolor glyphs. -
Some glyphs are drawn rather than taken from the font, the way Ghostty does it, with Ghostty's own drawing code:
- box drawing, block elements and shades;
- braille;
- Powerline's separators and branch symbols;
- geometric shapes;
- the symbols for legacy computing (sextants, octants, wedges).
Drawn to the cell, lines meet their neighbours without gaps and braille dots fill the cell, whatever the font's own versions look like.
-
- Tabs. A window holds as many terminals as it is given, with a strip of tabs above them once there are two. New tabs start in the directory the shown one is in.
- One process, many windows. The first harrier owns
dev.jcollie.harrieron the session bus and servesorg.freedesktop.Application. Later launches hand their windows to it with their command, working directory and environment, and launchers use the same interface through the desktop entry'sDBusActivatable=true. Windows opened this way take focus through xdg-activation. - A terminal for other programs to launch in. harrier implements the
Terminal Intent,
org.freedesktop.Terminal1, so a launcher can run a program whose desktop file saysTerminal=truein it without knowing harrier's command line. - Configured in struthio. Its programs can ask which machine they are running on.
Building and running
$ nix develop
$ zig build run # build, then run your shell
$ zig build run -- -e vim README.md
$ zig build test --summary all
The development shell has Zig 0.17.0, glslang for the shaders, the Vulkan
loader and its validation layer, and REUSE. --validation turns the layer
on.
The font libraries are built ReleaseSafe even in a Debug build. Reading fontconfig's cached patterns takes seconds when they are unoptimized.
The package and the VM test:
$ nix build # result/bin/harrier
$ nix build .#checks.x86_64-linux.sway # harrier in a NixOS VM
The package's Zig dependencies come from build.zig.zon.nix, which
zon2nix generates. After changing build.zig.zon, regenerate it:
$ nix develop -c zon2nix --17 --nix=build.zig.zon.nix build.zig.zon
The VM test runs harrier in a headless sway, with lavapipe standing in for a GPU. pixman offers no dma-bufs, so this run presents through shared memory. It checks that the screenshot has text in it.
Usage
harrier [options] [-e command [args...]]
harrier validate-config [--hostname NAME] FILE
-e, --command PROG ARGS... run PROG in the window instead of the shell
--serial PATH open a serial port instead of running a program
--baud RATE the serial port's rate (default 115200)
--config PATH the configuration file to read; also keeps
the window in this process
--tab open a tab in the harrier window last used,
rather than a window
--standalone open the window in this process rather than
handing it to a harrier already running
--service run as the session bus service, with no
window until one is asked for
--validation turn on Vulkan's validation layer
--version, -h, --help
The default key bindings:
| Keys | Action |
|---|---|
ctrl+shift+c / ctrl+shift+v |
copy / paste |
shift+insert, middle button |
paste the primary selection |
shift+page_up / shift+page_down |
scroll a page |
ctrl+shift+home / ctrl+shift+end |
scroll to the top / bottom |
ctrl+=, ctrl+-, ctrl+0 |
font size larger, smaller, as configured |
ctrl+shift+n / ctrl+shift+q |
new window / close window |
ctrl+shift+t / ctrl+shift+w |
new tab / close tab |
ctrl+page_down / ctrl+page_up, ctrl+tab / ctrl+shift+tab |
next / previous tab |
ctrl+shift+page_up / ctrl+shift+page_down |
move the tab left / right |
alt+1 … alt+8, alt+9 |
that tab, the last tab |
Holding shift takes the mouse back from a program that has asked for it, so that text can still be selected.
They are GNOME Terminal's. alt+1 to alt+9 are taken from programs that
use them, as GNOME Terminal takes them; .unbind them to give them back.
On the tab bar, a press shows a tab, the middle button or the × closes
one, and + or a double-click on the empty part opens one. Closing the last
tab closes the window. With keep-terminal-open or a program that could not
be started, a tab stays after its program has gone, and a key closes it.
Running as a service
zig build -Dbindir=/where/harrier/is installs four things beside the
binary. Each of the first three names the binary by its absolute path, which
is why the option exists; the Nix package passes it.
share/applications/dev.jcollie.harrier.desktop: the desktop entry, withDBusActivatable=true,Implements=org.freedesktop.Terminal1and a New Window action.share/dbus-1/services/dev.jcollie.harrier.service: lets the session bus start harrier when a window is asked for, through systemd.share/systemd/user/app-dev.jcollie.harrier.service: harrier as a user service,harrier --service. It starts with no window, opens the ones it is asked for, and exits five minutes after the last one closes (seeservice.idle_exit_seconds). Stopping it closes its windows.share/icons/hicolor/*/apps/dev.jcollie.harrier.png: the icon.
The Terminal Intent
The same object, /dev/jcollie/harrier, serves org.freedesktop.Terminal1
beside org.freedesktop.Application, as the Terminal
Intent asks. LaunchCommand opens one window with a tab
for each command it is given, as the specification suggests, or with the
shell for none:
execis the program and its arguments; empty runs the shell.envis set on top of harrier's environment rather than replacing it.working_directoryis where it starts. Without one, the desktop entry'sPathis used, and failing that$HOME.desktop_entry, when given, names the tabs after the entry'sName. One that is not a desktop entry refuses the call withorg.freedesktop.DBus.Error.InvalidArgs.keep-terminal-openkeeps each tab after its program exits, saying how it exited, until a key is pressed.- The activation token in
platform_datagoes to the window.
A program that cannot be started still gets its tab, saying why and
waiting for a key, and the rest of the commands are launched regardless.
The paths and arguments are ay; the NUL that GLib's bytestrings end with
is dropped.
To try it by hand, busctl takes each ay as a length and its bytes:
$ busctl --user call dev.jcollie.harrier /dev/jcollie/harrier \
org.freedesktop.Terminal1 LaunchCommand 'aa{sv}aya{sv}a{sv}' \
1 1 exec aay 1 3 116 111 112 \
0 1 keep-terminal-open b true 0
(gdbus call with b'…' literals nested in variants is no use here:
it sent corrupted bytes when this was tested.)
On NixOS, or with the Home Manager module:
{
environment.systemPackages = [ harrier ];
services.dbus.packages = [ harrier ];
systemd.packages = [ harrier ];
}
The service gets its environment from the systemd user manager, which needs
the graphical session's: WAYLAND_DISPLAY above all. Desktop sessions
export it at login with dbus-update-activation-environment --systemd WAYLAND_DISPLAY, or systemctl --user import-environment. A window handed
over from a shell still runs with that shell's environment and directory,
because the launch sends both.
Configuration
harrier reads harrier/config.struthio from the configuration directory
known-folders finds: $XDG_CONFIG_HOME, or ~/.config.
Without the file, it uses the defaults. A struthio file is ZON that can make
decisions:
.{
.font = .{
.family = "Iosevka Term",
.size = if (ctx.hostname == "laptop") 11 else 13,
.features = .{ "ss05" },
},
.colors = .{ .background = "#1d1f21", .foreground = "#c5c8c6" },
.cursor = .{ .style = .bar, .blink = false },
.command = .{ "fish", "--login" },
.keybind = .{
.{ .keys = "super+c", .action = .copy },
.{ .keys = "ctrl+shift+n", .action = .unbind },
},
}
ctx.hostname and ctx.os say where it is running, and @env("NAME")
reads the environment. Every field is in src/config/Config.zig:
-
font: the family, as a fontconfig pattern; the size in points; OpenType features; ligatures on or off; andbuiltin_glyphs, on by default, which draws box drawing, blocks, braille and the like to fit the cell rather than taking them from the font. -
colors: the foreground, the background, the cursor, and palette overrides. A color is anything libghostty-vt parses, X11 names included. -
cursor: the style and whether it blinks. -
window: the padding, the initial size in cells, and who draws the title bar and frame:.server(the default) asks the compositor, and harrier draws its own where the compositor will not, as on GNOME or weston;.clientalways draws harrier's;.nonedraws nothing.
And
tab_bar:.auto(the default) shows the tabs once there are two,.alwaysfrom the first,.nevernot at all. -
commandorserial: what the window shows.serialtakes the path, rate, data bits, parity, stop bits and flow control. -
term: whatTERMis set to (xterm-256color). -
scrollback_bytes: how much scrollback to keep. -
present:.auto,.dmabufor.shm. -
gpu: part of a Vulkan device's name ("Radeon","llvmpipe"), to choose one on a machine with several. Unset uses the device the compositor composites with. -
service:idle_exit_seconds, how long the service keeps running without a window. The default is 300; null keeps it running. -
keybind: bindings added on top of the defaults.
Checking a configuration
$ harrier validate-config ~/.config/harrier/config.struthio
/home/me/.config/harrier/config.struthio: colors.palette[3]: "gren" is not a color
/home/me/.config/harrier/config.struthio: keybind[0]: "hyper+x" is not a key harrier can read
validate-config evaluates the file and checks what struthio sees only as
strings: the colors, the key bindings and the font features. It reports
every problem rather than the first, exits 1 if there were any, and opens no
window. A running harrier would refuse to start over a bad color but only
warn about a bad binding or feature; this treats them all as mistakes.
--hostname NAME sets what the file sees as ctx.hostname, to check a
file meant for another machine.
Home Manager
The flake has a Home Manager module, homeModules.harrier. It installs
harrier, writes its configuration, and installs the D-Bus service file and
systemd user unit through dbus.packages and systemd.user.packages, so
that one harrier opens every window.
The configuration is a struthio file, given as text or as a file, not generated from Nix settings:
{
inputs.harrier.url = "git+https://git.jcollie.dev/jeff/harrier.git";
# in a Home Manager configuration:
imports = [ inputs.harrier.homeModules.harrier ];
programs.harrier = {
enable = true;
configText = ''
.{
.font = .{ .family = "Iosevka Term", .size = if (ctx.hostname == "laptop") 11 else 13 },
}
'';
# or: configFile = ./harrier.struthio;
# what ctx.hostname is while it is checked; the sandbox is `localhost`
validateHostname = "laptop";
};
}
configTextorconfigFile: one or the other, or neither for the defaults. A Nix path inconfigFileis copied into the store; a string naming a file outside it ("/home/me/dotfiles/harrier.struthio") is linked to where it is, so editing it needs no rebuild.validate(on by default): the file is run throughharrier validate-configas it is built, so a mistake failshome-manager switchrather than the next window. A file linked from outside the store cannot be read by the build and is not checked. The check runs in the build sandbox, where@envfinds almost nothing set.validateHostname: whatctx.hostnameis during the check.service.enable(on by default): the D-Bus and systemd user service. The service reads its configuration when it starts, and stopping it closes every window, so switching does not restart it; a new configuration applies once it next starts.package: harrier from this flake, by default.
Design
┌────────────── main thread ──────────────┐
compositor ◀──────▶│ platform ─▶ Window ─▶ frame.Builder │
(Wayland) │ (events) │ ─▶ Renderer ─▶ Target ─▶ dma-buf / shm
│ │ lock │
└────────────────┼─────────────────────────┘
▼
termio.Session (Terminal + stream, behind a mutex)
▲ nextSlice │ outbox
IO thread ──┘ ▼
Backend: Pty | Serial
backend/: what is on the other side.Backendis a tagged union:Pty, which forks the program onto a pseudo-terminal using raw system calls, andSerial.- A Windows port adds a ConPTY variant.
Activation: the session bus. It owns the name and servesorg.freedesktop.Applicationandorg.freedesktop.Terminal1, on a thread of its own, through zig-dbus-service. Its handlers only queue the windows and tabs asked for and wake the main loop. It is also the client a launch uses to hand its window, or with--tabits tab, over: thenew-windowandnew-tabactions.termio/Session: one terminal.- Its IO thread is the only reader and writer of the backend. It feeds what it reads through libghostty-vt's stream with the terminal's lock held.
- Keystrokes, and the terminal's own replies to queries, go out through a locked outbox, so the main thread never blocks on a slow port.
platform/: the window system, chosen at compile time.- The Wayland backend speaks in the portable types of
Event.zig. - A Windows backend would produce the same types from window messages.
- The Wayland backend speaks in the portable types of
renderer/:frame.Builderturns libghostty-vt'sRenderStateinto rectangles.vulkan/Rendererdraws them in one instanced call.- A
Targetsays where the frame goes. Either it hands the renderer a framebuffer on its own image to draw into, or the renderer draws offscreen and the target copies the image out. The targets arewayland/Dmabuf, which does either, andwayland/Shm, which copies. A Windows target would be a swapchain onVK_KHR_win32_surface.
WindowandTab: a window is the platform window, the renderer and its tabs; a tab is a session and what the window keeps about it — what was last drawn of it, its selection, its title. Only the shown tab is drawn. Every tab's session is polled, so a hidden program's output is read and its exit noticed, and every tab is resized with the window.decor/Decorations: harrier's own title bar and frame, drawn by the same renderer as the grid. It is the insets around the grid, the hit-test that turns a press into a move or resize request, and the quads that draw it. Nothing in it is specific to Wayland.decor/TabBar: the strip of tabs, drawn by harrier under whoever's title bar, likewise as insets, a hit-test and quads.font/:Grid: the four styled fallback lists, and the cell.shape: one row at a time, with each cell's index as its cluster.Glyphs: rasterized once, into one of two atlases.sprite/: the glyphs harrier draws itself.draw/andcanvas.zigare Ghostty'ssrc/font/sprite, drawing with z2d;Sprite.zigcollects thedraw<CP>anddraw<MIN>_<MAX>functions into a table of ranges at compile time, as Ghostty'sFace.zigdoes. The shaper hands such a cell over without shaping it.Atlas: a skyline packer ported from Ghostty's, after Jylänki.
Testing
zig build test covers:
- the pty and serial backends, against real pseudo-terminals;
- the title bar and frame: insets, and what is under the pointer at each edge, corner and button;
- a session end to end: a program's output, a device-attributes query answered back through the pty, titles, OSC 52, keystrokes, resizes;
- configuration and key bindings, and
validate-config's checks; - the tab bar: when it shows, and what is under the pointer as tabs multiply and narrow;
- the atlas, and shaping and rasterizing with whatever fonts the machine has;
- the built-in glyphs: every code point in every range draws, and lines and blocks land on the cell's edges.
The Wayland and Vulkan code is compiled by the tests, run in the VM test,
and exercised by running it. It has been run under a headless sway: typing, Compose, dead
keys, CJK fallback, 256-color and SGR rendering, dma-buf presentation with
explicit sync, a serial port through a socat pty pair, and harrier's own
decorations, both forced on sway and falling back on weston, and the built-in
glyphs beside the font's own. Drawing straight
into a dma-buf has been run on lavapipe (gpu = "llvmpipe"), whose images
sway imports, including a resize, with the validation layer clean. The RX 480
it was developed on has no modifier support, so there it copies.
nix flake check runs the package's tests in the sandbox and these NixOS
VM tests:
sway: a window on a headless sway, with text in it;activation: two launches, one systemd user service started by the session bus, taking both windows;--tab, opening a tab in the last window rather than a window; thenLaunchCommand: one window with a tab per command, named after the desktop entry, one held after its program exits and one saying its program does not exist, a bad desktop entry refused, and keys closing the held tabs and then the window;home-manager: the Home Manager module alone: the configuration written where harrier reads it, the service found through what the module linked, and a window drawn in the configured background.
config-rejected checks that a configuration with mistakes in it fails the
build that wants it, naming each one.
Not yet
- Not drawn yet:
- splits, and dragging tabs about or out into a window of their own;
- a confirmation for pastes that contain a newline (they go through as xterm sends them);
- kitty graphics.
- Emoji: emoji fonts made of bitmaps (
CBDT,sbix) are not drawn, so emoji come from whatever outline font has them. - Platforms: the BSDs and macOS have no serial backend in zig-serial, and nothing here has a Windows backend.
Where this lives
The canonical repository is on my Forgejo instance, and Tangled carries a mirror of it:
git clone https://git.jcollie.dev/jeff/harrier.git
- Forgejo: https://git.jcollie.dev/jeff/harrier
- Tangled: https://tangled.org/jcollie.dev/harrier
CI runs on the Forgejo repository.
License
MIT; see LICENSES/MIT.txt. The project follows the REUSE specification.
src/font/Atlas.zig and everything in src/font/sprite/ but Sprite.zig
and font.zig are ported from Ghostty, © 2024 Mitchell Hashimoto and
Ghostty contributors, also MIT.
The icon, in dist/icons, is cropped from Becky Matsubara's photograph
Circus hudsonius, male perched, Berkeley, California (2018),
licensed CC BY 2.0. It was scaled and converted to
PNG.
References cited
- Hashimoto, M., & Ghostty contributors. libghostty-vt, in Ghostty [Computer software]. https://github.com/ghostty-org/ghostty
- Ghostty contributors. Update to Zig 0.17 (Pull request #14519). GitHub. https://github.com/ghostty-org/ghostty/pull/14519
- Jylänki, J. (2010). A Thousand Ways to Pack the Bin — A Practical Approach to Two-Dimensional Rectangle Bin Packing. https://github.com/juj/RectangleBinPack/blob/master/RectangleBinPack.pdf
- Rougier, N. P. freetype-gl [Computer software]. https://github.com/rougier/freetype-gl
- Moy, T. XTerm Control Sequences. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
- Goyal, K. The kitty keyboard protocol. https://sw.kovidgoyal.net/kitty/keyboard-protocol/
- The Khronos Group. Vulkan 1.4 Specification. https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html
- Snektron. vulkan-zig [Computer software]. https://github.com/Snektron/vulkan-zig
- The Wayland project. The Wayland Protocol. https://wayland.freedesktop.org/docs/book/
- The Wayland project. Wayland (Version 1.26.0) and wayland-protocols (Version 1.49) [Computer software]. freedesktop.org. https://gitlab.freedesktop.org/wayland/wayland-protocols
- The Linux kernel developers. Buffer Sharing and Synchronization (dma-buf). https://docs.kernel.org/driver-api/dma-buf.html
- The Linux kernel developers. drm_fourcc.h and input-event-codes.h. Linux kernel source. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi
- The Linux man-pages project. ioctl_tty(2) and pts(4). Linux manual pages. https://man7.org/linux/man-pages/man2/ioctl_tty.2.html
- The xkbcommon project. libxkbcommon [Computer software] and The XKB keymap text format, V1. https://xkbcommon.org/doc/current/keymap-text-format-v1.html
- X.Org Foundation. keysymdef.h. xorgproto. https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/blob/master/include/X11/keysymdef.h
- Microsoft. OpenType Specification. https://learn.microsoft.com/en-us/typography/opentype/spec/
- Fontconfig [Computer software]. freedesktop.org. https://gitlab.freedesktop.org/fontconfig/fontconfig
- freedesktop.org. XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/latest/
- ziglibs. known-folders [Computer software]. https://github.com/ziglibs/known-folders
- Vanderson, D., & contributors. dvui [Computer software], whose Vulkan backend's dma-buf presentation harrier adapts. https://github.com/david-vanderson/dvui
- Gibson, E., & contributors. wio [Computer software], whose native Wayland backend harrier's follows. https://git.jcollie.dev/jeff/wio
- Ollie, J. C. zig-wayland-native, zig-xkb, zig-font, zig-font-config, zig-font-renderer, zig-font-shaper, struthio and zig-serial [Computer software]. https://git.jcollie.dev/jeff
- Matsubara, B. (2018). Circus hudsonius, male perched, Berkeley, California (cropped) [Photograph]. Wikimedia Commons. CC BY 2.0. https://commons.wikimedia.org/wiki/File:Circus_hudsonius,_male_perched,_Berkeley,_California_(cropped).jpg
- freedesktop.org. Desktop Entry Specification: D-Bus Activation. https://specifications.freedesktop.org/desktop-entry/latest/dbus.html
- Pennington, H., Carlsson, A., Larsson, A., Herzberg, S., McVittie, S., & Zeuthen, D. D-Bus Specification. freedesktop.org. https://dbus.freedesktop.org/doc/dbus-specification.html
- The systemd project. systemd.service — Service unit configuration. https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
- The Wayland project. xdg-activation-v1. wayland-protocols. https://wayland.app/protocols/xdg-activation-v1
- Brown, Z., Faure, D., McCombs, T., & Wick, S. (2026). Terminal Intent (Version 0.1, draft). freedesktop.org. https://specifications.freedesktop.org/terminal-intent/latest/
- freedesktop.org. Desktop Entry Specification. https://specifications.freedesktop.org/desktop-entry/latest/
- Home Manager contributors. Home Manager [Computer software]. https://github.com/nix-community/home-manager
- Ollie, J. C. zig-dbus-service [Computer software]. https://git.jcollie.dev/jeff/zig-dbus-service
- Culverhouse, T., & Ollie, J. C. mdbus [Computer software]. https://git.jcollie.dev/jeff/mdbus
- Zig Software Foundation. The Zig programming language [Computer software]. https://ziglang.org/
- Free Software Foundation Europe. REUSE Specification (Version 3.3). https://reuse.software/spec-3.3/