GPU-accelerated terminal emulator for the gardesk desktop
garterm is a modern terminal emulator with GPU-accelerated rendering via wgpu, tabs, split panes, configurable keybindings, and Lua scripting support. It integrates with the gar ecosystem through shared configuration and IPC, supporting sessions, color schemes, and extensive customization.
Alt+T - New tabAlt+W - Close tab/paneAlt+] / Alt+[ - Next/prev tabAlt+1-9 - Jump to tabAlt+H - Split horizontalAlt+V - Split verticalAlt+Arrow - Focus pane directionCtrl+Shift+Alt+Arrow - Resize paneCtrl+Shift+C - Copy selectionCtrl+Shift+V - Paste clipboardCtrl+Shift+=/-/0 - Zoom in/out/resetCtrl+Shift+F - Search scrollbackF11 - Toggle fullscreenCtrl+Shift+R - Reload configVulkan-first rendering via wgpu with OpenGL fallback. Glyph atlas caching for fast redraws.
Multiple tabs with BSP tree-based split layouts. Each pane has independent PTY and state.
Configure via TOML or integrate with gar's init.lua for shared settings and sessions.
Built-in presets: Tokyo Night, Catppuccin, Dracula, Nord, Solarized, Gruvbox, One Dark.
Clickable URLs with configurable modifier key. Opens in default browser via xdg-open.
Define named sessions in Lua with tab layouts, commands, and working directories.
garterm loads configuration from multiple sources in priority order:
~/.config/gar/init.lua (gar.terminal table)~/.config/garterm/config.toml| Option | Type | Default | Description |
|---|---|---|---|
--config, -c | path | ~/.config/garterm/config.toml | Configuration file path |
--command, -e | string | $SHELL | Command to execute instead of shell |
--working-directory | path | inherited | Starting working directory |
--title | string | "garterm" | Window title |
--font-size | f32 | 14.0 | Font size in points |
--vsync | flag | false | Enable VSync-based rendering (dirty-flag only) |
--print-config | flag | false | Print resolved configuration and exit |
# ~/.config/garterm/config.toml
[general]
shell = "/bin/zsh"
shell_args = ["-l"]
vsync = false
log_level = "info"
[font]
family = "JetBrains Mono"
size = 12.0
bold_is_bright = false
ligatures = false
[window]
title = "garterm"
class = "garterm"
padding = [0, 0]
columns = 120
rows = 36
opacity = 0.95
[terminal]
scrollback_lines = 10000
scroll_lines = 3
bracketed_paste = true
clipboard_write = true
clipboard_read = false
close_on_exit = true
term = "xterm-256color"
[mouse]
copy_on_select = true
middle_click_paste = true
right_click = "paste"
url_detection = true
url_modifier = "ctrl"
[bell]
visual = true
visual_duration_ms = 100
audio = false
[colors]
preset = "tokyo-night"
# Override specific colors:
# background = "#000000"
[tab_bar]
height = 24
position = "top"
show_single_tab = false
[keybinds]
"alt+t" = "new_tab"
"alt+w" = "close_pane"
"alt+h" = "split_horizontal"
"alt+v" = "split_vertical" | Option | Type | Default | Description |
|---|---|---|---|
shell | string | $SHELL or /bin/sh | Shell command to execute |
shell_args | string[] | ["-l"] | Arguments to pass to shell |
working_directory | path? | inherited | Starting directory for new terminals |
vsync | bool | false | Use VSync-based rendering (dirty-flag only redraws) |
log_level | string | "info" | Log level: error, warn, info, debug, trace |
| Option | Type | Default | Description |
|---|---|---|---|
family | string | "monospace" | Primary font family name |
size | f32 | 14.0 | Font size in points (6-72) |
bold_family | string? | same as family | Font family for bold text |
italic_family | string? | same as family | Font family for italic text |
bold_is_bright | bool | false | Use bold font for bright ANSI colors |
ligatures | bool | false | Enable font ligatures (requires swash) |
letter_spacing | f32 | 0.0 | Extra horizontal spacing between characters (pixels) |
line_spacing | f32 | 0.0 | Extra vertical spacing between lines (pixels) |
| Option | Type | Default | Description |
|---|---|---|---|
title | string | "garterm" | Window title (overridable via OSC 0/2) |
class | string | "garterm" | X11 window class for window manager rules |
padding | [i32, i32] | [0, 0] | Window padding [horizontal, vertical] in pixels |
columns | u32 | 80 | Initial terminal width (0 = auto) |
rows | u32 | 24 | Initial terminal height |
opacity | f32 | 1.0 | Window opacity (0.0-1.0, requires compositor) |
fullscreen | bool | false | Start in fullscreen mode |
| Option | Type | Default | Description |
|---|---|---|---|
scrollback_lines | u32 | 10000 | Maximum scrollback history lines |
scroll_lines | u32 | 3 | Lines to scroll per mouse wheel tick |
bracketed_paste | bool | true | Wrap pasted text in escape sequences |
clipboard_write | bool | true | Allow programs to write clipboard (OSC 52) |
clipboard_read | bool | false | Allow programs to read clipboard (security risk) |
close_on_exit | bool | true | Close pane when shell exits |
hold_on_exit | bool | false | Keep pane open after shell exits, show exit code |
term | string | "xterm-256color" | TERM environment variable value |
| Option | Type | Default | Description |
|---|---|---|---|
copy_on_select | bool | true | Auto-copy selection to PRIMARY clipboard |
middle_click_paste | bool | true | Paste PRIMARY on middle click |
right_click | string | "paste" | Right click action: paste, context_menu, none |
word_chars | string | "-_" | Extra characters for word selection (double-click) |
url_detection | bool | true | Make URLs clickable |
url_modifier | string? | "ctrl" | Modifier for URL click: ctrl, shift, alt, or null |
| Option | Type | Default | Description |
|---|---|---|---|
visual | bool | true | Flash screen on bell |
visual_duration_ms | u32 | 100 | Visual bell duration in milliseconds |
audio | bool | false | Play audio on bell |
audio_command | string? | none | Command to play bell (e.g., "paplay bell.ogg") |
| Option | Type | Default | Description |
|---|---|---|---|
height | u32 | 24 | Tab bar height in pixels |
position | string | "top" | Tab bar position: top or bottom |
show_single_tab | bool | false | Show tab bar with only one tab |
max_tab_width | f32 | 200.0 | Maximum tab width in pixels |
tab_padding | f32 | 16.0 | Horizontal padding inside tabs |
shorten_paths | bool | true | Shorten paths in tab titles (~/P/a/src) |
background | [f32; 4] | [0.08, 0.08, 0.12, 1.0] | Tab bar background color [R, G, B, A] |
active_bg | [f32; 4] | [0.15, 0.15, 0.20, 1.0] | Active tab background color |
inactive_bg | [f32; 4] | [0.10, 0.10, 0.14, 1.0] | Inactive tab background color |
garterm is a GPU-accelerated terminal emulator designed for the gardesk ecosystem. It provides modern features like tabs, BSP-tree splits, Lua scripting, and IPC control while maintaining high performance through wgpu-based rendering with glyph atlas caching.
The terminal uses the VTE crate for ANSI/CSI escape sequence parsing, supporting full xterm-256color compatibility including mouse modes, alternate screen buffer, OSC sequences (title, hyperlinks, clipboard), and bracketed paste.
garterm consists of several interconnected components working together to provide a responsive terminal experience.
The main event loop integrates window management (X11 via gartk), event polling (keyboard, mouse, X11 events), IPC server for remote control, and the Lua runtime for scripting. Events flow through keybind lookup to actions that modify terminal state.
X11 Event → App.run()
↓
Keyboard/Mouse Handler → Keybind Lookup → Action
↓
Terminal State Update → Dirty Flag → Render
↓
GPU Render (grid → glyphs → framebuffer)
↓
X11 Present garterm uses wgpu for cross-platform GPU abstraction, preferring Vulkan for higher texture limits (avoiding GL's 2048 texture limit) with OpenGL fallback. X11 integration uses Xlib handles for surface creation.
Rendering pipeline:
Rendering modes: VSync mode only redraws on changes (low CPU). Continuous 60fps timer mode (default) redraws every frame, required on Asahi Linux where VSync may not work correctly.
The terminal uses the vte crate for escape sequence parsing,
supporting:
Font rendering uses the fontdue library for glyph rasterization. garterm loads system fonts with fallback chain: specified family → JetBrains Mono → DejaVu Sans Mono.
Separate font variants (Regular, Bold, Italic, BoldItalic) can be configured. Cell metrics are calculated per font to ensure proper grid alignment. Fallback fonts handle symbols, box drawing characters, and Nerd Font glyphs.
Each tab contains an independent split tree layout. Tabs maintain their own state including working directory, terminal mode, and scroll position.
Alt+T - Create new tabAlt+W - Close current tab/paneAlt+] / Alt+[ - Next/previous tabAlt+1-9 - Jump to tab by numberPanes use a BSP (Binary Space Partition) tree structure allowing arbitrary layouts. Each pane has its own PTY terminal with independent state.
Alt+H - Split horizontally (side-by-side)Alt+V - Split vertically (stacked)Alt+Up/Down/Left/Right - Focus pane directionCtrl+Shift+Alt+Arrow - Resize pane (5px per press)
The tab bar shows all open tabs with configurable position (top/bottom), height,
and colors. Tabs show the current working directory with optional path shortening
(~/P/r/src instead of full path).
Tab width is flexible up to max_tab_width, shrinking
proportionally when many tabs are open. The tab bar can be hidden when only one tab is open.
garterm maintains a scrollback buffer (default 10,000 lines) for terminal history.
Shift+PageUp/PageDown - Scroll by pageCtrl+Shift+Home - Jump to topCtrl+Shift+End - Jump to bottomCtrl+Shift+K - Clear scrollbackscroll_lines (default 3)garterm supports both PRIMARY (selection) and CLIPBOARD (Ctrl+C/V) X11 clipboards.
Ctrl+Shift+C - Copy selection to CLIPBOARDCtrl+Shift+V - Paste from CLIPBOARDcopy_on_select - Auto-copy selection to PRIMARYmiddle_click_paste - Paste PRIMARY on middle clickclipboard_write: true)
When url_detection is enabled (default), garterm
detects http(s)://, ftp://, and file:// URLs in terminal output. URLs become clickable
with the configured modifier key (default: Ctrl).
Clicking opens the URL with xdg-open.
Press Ctrl+Shift+F to enter search mode.
Type to search scrollback history.
N - Next matchShift+N - Previous matchCtrl+I - Toggle case sensitivityEscape - Exit search
The terminal bell can be configured for visual flash (visual: true),
audio command (audio_command), or both. Visual bell
briefly inverts colors for the configured duration.
garterm integrates with gar's Lua configuration system, allowing advanced customization including function keybinds and named sessions.
Configure garterm in ~/.config/gar/init.lua:
gar.terminal = {
shell = "/bin/zsh",
vsync = false,
font = {
family = "JetBrains Mono",
size = 12.0,
bold_is_bright = false,
},
window = {
title = "garterm",
padding = { 0, 0 },
opacity = 0.95,
},
colors = {
preset = "catppuccin-mocha",
},
mouse = {
copy_on_select = true,
url_detection = true,
url_modifier = "ctrl",
},
tab_bar = {
height = 28,
position = "top",
show_single_tab = false,
},
} Define named sessions with predefined tab layouts, commands, and working directories:
gar.terminal.sessions = {
webdev = {
tabs = {
{
title = "Frontend",
cwd = "~/Projects/frontend",
cmd = "npm run dev",
splits = {
{ direction = "horizontal", cmd = "npm test" },
}
},
{
title = "Backend",
cwd = "~/Projects/backend",
cmd = "python -m flask run"
},
{
title = "Editor",
cwd = "~/Projects",
cmd = "nvim ."
},
}
}
}
Load sessions with gartermctl load-session webdev
or bind to a key: "alt+w" = "load_session:webdev"
Lua keybinds can call terminal API functions for advanced automation:
gar.terminal.keybinds = {
["alt+e"] = function()
-- Split and open editor
gar.terminal.split({ direction = "vertical" })
gar.terminal.send_text(nil, "nvim .\n")
end,
["ctrl+alt+n"] = { action = "new_tab" },
["alt+s"] = { action = "load_session", session = "webdev" },
}
-- Available API functions:
-- gar.terminal.new_tab({ cwd, cmd, title })
-- gar.terminal.split({ direction, cwd, cmd })
-- gar.terminal.send_text(pane_id, text)
-- gar.terminal.close_tab(tab_id)
-- gar.terminal.close_pane(pane_id)
-- gar.terminal.focus_tab(index)
-- gar.terminal.focus_direction(dir)
-- gar.terminal.next_tab()
-- gar.terminal.prev_tab()
-- gar.terminal.load_session(name) garterm includes several built-in color presets:
tokyo-night (default) Deep blue/purple theme
catppuccin-mocha Warm pastel dark theme
gruvbox-dark Retro earthy colors
dracula Purple-accented dark theme
nord Arctic blue theme
solarized-dark Precision color theme
one-dark Atom editor theme
Override individual colors on top of any preset:
[colors]
preset = "tokyo-night"
# Override specific colors (hex format)
foreground = "#c0caf5"
background = "#1a1b26"
cursor = "#c0caf5"
selection = "#33467c"
# ANSI 0-7 (normal)
black = "#15161e"
red = "#f7768e"
green = "#9ece6a"
yellow = "#e0af68"
blue = "#7aa2f7"
magenta = "#bb9af7"
cyan = "#7dcfff"
white = "#a9b1d6"
# ANSI 8-15 (bright)
bright_black = "#414868"
bright_red = "#f7768e"
# ... etc | Key | Action |
|---|---|
Ctrl+Shift+C | Copy selection to clipboard |
Ctrl+Shift+V | Paste from clipboard |
| Key | Action |
|---|---|
Alt+T | New tab |
Alt+W | Close pane (or close tab if last pane) |
Alt+] | Next tab |
Alt+[ | Previous tab |
Alt+1-9 | Jump to tab 1-9 |
| Key | Action |
|---|---|
Alt+H | Split horizontal (side-by-side) |
Alt+V | Split vertical (stacked) |
Alt+Up/Down/Left/Right | Focus pane in direction |
Ctrl+Shift+Alt+Arrow | Resize pane (5px per press) |
| Key | Action |
|---|---|
Shift+PageUp | Scroll page up |
Shift+PageDown | Scroll page down |
Ctrl+Shift+Home | Scroll to top |
Ctrl+Shift+End | Scroll to bottom |
| Key | Action |
|---|---|
Ctrl+Shift+= | Increase font size |
Ctrl+Shift+- | Decrease font size |
Ctrl+Shift+0 | Reset font size |
| Key | Action |
|---|---|
Ctrl+Shift+F | Search forward in scrollback |
Ctrl+Shift+R | Reload configuration |
F11 | Toggle fullscreen |
Ctrl+Shift+Del | Reset terminal |
Ctrl+Shift+K | Clear scrollback |
Override or add keybindings in the [keybinds] section:
[keybinds]
# Tab management
"alt+t" = "new_tab"
"alt+w" = "close_pane"
"alt+]" = "next_tab"
"alt+[" = "prev_tab"
"super+Return" = "new_tab"
# Split management
"alt+h" = "split_horizontal"
"alt+v" = "split_vertical"
# Session loading
"alt+s" = "load_session:webdev"
# Disable a default binding
"ctrl+shift+c" = "none" copy, paste, paste_primary new_tab, close_tab, next_tab, prev_tab, tab_1..tab_9 split_horizontal, split_vertical, close_pane, focus_up/down/left/right, resize_up/down/left/right scroll_up, scroll_down, scroll_page_up, scroll_page_down, scroll_to_top, scroll_to_bottom increase_font_size, decrease_font_size, reset_font_size reload_config, toggle_fullscreen, reset_terminal, clear_scrollback, search_forward, load_session:name, none
Each garterm instance listens on a Unix socket for remote control. The
gartermctl utility sends JSON commands to control the terminal.
Socket location: $XDG_RUNTIME_DIR/garterm/garterm-{PID}.sock
Focus file: $XDG_RUNTIME_DIR/garterm/focused tracks the last focused window.
| Command | Parameters | Description |
|---|---|---|
new_window | cwd?, exec? | Open a new garterm window |
new_tab | cwd?, startup_cmd?, title? | Create a new tab in focused window |
close_tab | none | Close the current tab |
next_tab | none | Switch to the next tab |
prev_tab | none | Switch to the previous tab |
switch_tab | index | Switch to tab by index (0-based) |
split | direction, cwd?, startup_cmd? | Split current pane |
close_pane | none | Close the focused pane |
focus_pane_direction | direction (up/down/left/right) | Focus pane in direction |
resize_pane | direction, amount | Resize focused pane |
send_text | text | Send text to terminal |
load_session | name | Load a named session |
get_info | none | Get window/tab/pane info |
reload | none | Reload configuration |
quit | none | Close garterm |
ping | none | Health check |
Direct socket communication uses JSON format:
# Create new tab
{ "cmd": "new_tab", "cwd": "/home/user/projects", "title": "Dev" }
# Split pane
{ "cmd": "split", "direction": "vertical", "startup_cmd": "htop" }
# Send text
{ "cmd": "send_text", "text": "ls -la\n" }
# Response format
{ "success": true, "message": "Tab created", "data": { "tabs": 2 } } garterm can be integrated with garbar for quick terminal access:
-- ~/.config/gar/init.lua
gar.keybinds = {
-- Open new terminal with Super+Return
["super+Return"] = function()
os.execute("garterm &")
end,
-- Open terminal in specific directory
["super+shift+Return"] = function()
os.execute("garterm --working-directory ~/projects &")
end,
} If you see a blank window, the GPU initialization may have failed:
Asahi Linux users: The default timer-based rendering (vsync=false) is required. VSync mode may not work correctly on Apple Silicon.
If text appears as boxes or wrong characters:
If programs like vim, htop, or tmux don't work correctly:
# Check TERM is set correctly
echo $TERM
# Should output: xterm-256color
# If not, add to config:
[terminal]
term = "xterm-256color" If gartermctl fails to connect:
Enable verbose logging for troubleshooting:
If garterm uses excessive CPU even when idle:
# The default is timer-based 60fps rendering
# Try VSync mode for lower CPU (if your GPU supports it)
[general]
vsync = true
# If VSync causes issues, the timer mode is required