A cross-platform, GPU-free terminal emulator written in Rust with vim-style modal input, split panes, and multi-tab sessions.
Renders entirely via a CPU pixel buffer — no GPU, no OpenGL, no Vulkan.
- Modal input — Insert, Normal, Visual, and Search modes (vim-style)
- Split panes — binary-tree layout, horizontal and vertical splits; drag separators or use
Ctrl+Shift+Arrowto resize - Multi-tab — independent pane trees and font metrics per tab
- Scrollback search — live match highlighting across 10 000-line buffer; history navigable with ↑/↓, persisted to
~/.config/mmterm/search_history - Themes — 10 built-in themes (including
ereader, a warm parchment/sepia theme for long sessions); custom themes via~/.config/mmterm/themes/; each named scope remembers its own theme independently of the global config - OSC 8 hyperlinks — clickable URLs rendered in the terminal
- OSC 52 clipboard sync — copy/paste over SSH without extra tools
- Focus reporting —
?1004h/lsends\e[I/\e[Oon window, tab, and pane focus changes; neovimautoreadand tmux work correctly - DEC line drawing — box-drawing characters for ncurses TUI apps (
dialog,nmtui,mutt) - Pane zoom — full-window focus for the active pane
- Passthrough mode —
Ctrl+Bforwards all keystrokes directly to the PTY, bypassing mmterm shortcuts; useful when running vim, tmux, or other apps that share keybindings; status bar showsINSERT PASS; pressCtrl+Bagain to exit - Session logging — capture PTY output per-pane to
~/.mmterm/withCtrl+Shift+L - Color emoji — rendered via FreeType CBDT/CBLC
- Visual bell — BEL (0x07) shows a
●dot next to the mode badge in the status bar for 150 ms; a 500 ms cooldown prevents spam from tab-completion; an optional screen flash is available viavisual_bell = truein[general] - Session persistence — tabs, splits, and per-pane CWDs are saved on quit and restored on next launch; a centered dialog asks
[s] Save and quit / [q] Quit / [Esc] Cancel; toggle withrestore_sessionin[general]; named scopes (--scope <name>) keep isolated session files and each retain their own theme - Command palette —
Ctrl+Shift+Pfuzzy-filter and run any action by name; shows the keyboard shortcut for each entry - TUI config editor — edit settings in-process with
Ctrl+, - Zero-config startup — bundled JetBrains Mono fallback font (regular, bold, italic)
- Rust 1.85+ (edition 2024)
- Linux (X11 or Wayland) or macOS
- On Linux: a C toolchain and FreeType headers (
libfreetype-dev)
cargo build --releaseThe binary is at target/release/mmterm.
Download the prebuilt binary into ~/.local/bin:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/roramirez/mmterm/main/install.sh)"The script verifies the download's SHA-256 checksum before installing and adds an
application-menu entry. If ~/.local/bin is not already on your PATH, the installer adds
it to your shell profile (~/.zshrc or ~/.bashrc).
Prefer to read before you run?
curl -fsSL https://raw.githubusercontent.com/roramirez/mmterm/main/install.sh -o install.sh
less install.sh # inspect
sh install.shEnvironment variables:
| Variable | Default | Effect |
|---|---|---|
MMTERM_BIN_DIR |
~/.local/bin |
Install directory |
MMTERM_VERSION |
latest release | Pin a specific tag, e.g. v0.5.0 |
sh -c "$(curl -fsSL https://raw.githubusercontent.com/roramirez/mmterm/main/install.sh)"On macOS this downloads mmterm-macos-aarch64.dmg, verifies its checksum, and opens it.
Drag mmterm.app onto the Applications folder.
mmterm.app is ad-hoc signed but not notarized. The first time you launch it,
right-click the app and choose Open, then confirm in the dialog. You only need to
do this once. (Depending on your macOS version and how the .dmg was obtained,
double-clicking the first time may be blocked by Gatekeeper — the right-click → Open
path always works.)
Verify the build (optional, recommended). Inspect the installer before running it, and verify the release's build provenance with the GitHub CLI:
# Inspect the script first
curl -fsSL https://raw.githubusercontent.com/roramirez/mmterm/main/install.sh | less
# Verify provenance of the downloaded disk image
gh attestation verify mmterm-macos-aarch64.dmg --repo roramirez/mmtermRequires Rust 1.85+ (edition 2024). On Linux you also need a C toolchain and FreeType
headers (libfreetype-dev).
cargo install --git https://github.com/roramirez/mmtermOr from a local checkout:
cargo install --path .mmtermPrint version:
mmterm --version # e.g. mmterm 0.3.0+abc1234 (local) or mmterm 0.3.0 (release)Print help:
mmterm --helpEnable logging:
RUST_LOG=info mmtermEnable debug logging to file:
mmterm --debug # writes DEBUG-level logs to ~/.mmterm/debug-<timestamp>.logOn first run, a config file is created at:
- Linux/macOS:
$XDG_CONFIG_HOME/mmterm/config.toml(defaults to~/.config/mmterm/config.toml)
[font]
family = "Noto Sans Mono"
size = 16.0
[window]
width = 800
height = 600
title = "mmterm"
cursor_blink_ms = 500
[shell]
# program = "/bin/zsh" # defaults to $SHELL
[terminal]
scrollback_lines = 10000 # minimum 100
[logging]
auto_log = false # start logging automatically for every new pane
log_dir = "" # destination directory (empty = ~/.mmterm)
[status_bar]
right = "%pwd %date{%H:%M}" # format string (%pwd = OSC 7 cwd, %date{fmt} = strftime)
[theme]
name = "default" # see ~/.config/mmterm/themes/ for available themes
[colors]
background = "#121212"
foreground = "#a0a0a0"
cursor = "#bbbbbb"
selection = "#3d3d3d"
palette = [ ... ] # 16-color ANSI paletteYou can also edit settings live with Ctrl+,.
mmterm checks GitHub for a newer release at most once per day (one request on startup).
- Linux: by default, an
↑X.Y.Zbadge appears in the status bar when an update is available; re-run the installer to update. You can opt in to silent background self-update (download + checksum-verify + atomically replace the binary, applied on next launch) by settingauto_update_install = true; this only applies to user-writable installs (~/.local/bin,~/.cargo/bin), never system installs. - macOS: an
↑X.Y.Zbadge appears in the status bar; click it to download and open the.dmg, then drag mmterm.app to Applications.
Disable via ~/.config/mmterm/config.toml:
[general]
auto_update_check = false # turn off the daily check + badge entirely
auto_update_install = true # Linux: opt in to silent self-replace (default: off)mmterm renders at the scale factor of the monitor the window is currently on, so text and UI chrome are crisp and correctly sized on Retina / high-DPI displays — and it adapts live when you drag the window between monitors of different DPI (no restart).
font.size (and the Ctrl+/Ctrl- font controls) are logical sizes: the perceived
size stays consistent across displays of any density. If you previously increased
font.size to compensate for tiny rendering on a HiDPI screen, reset it to a normal value.
| Binding | Action |
|---|---|
Ctrl+Q |
Quit — when restore_session = true shows save-session dialog; otherwise confirmation overlay when multiple tabs/panes are open |
Ctrl+Enter |
Toggle borderless fullscreen |
Ctrl+, |
Open config panel |
Ctrl+Shift+P |
Open command palette |
Ctrl+T |
New tab |
Ctrl+PageUp / Ctrl+PageDown |
Previous / next tab |
Ctrl+Shift+PageUp / Ctrl+Shift+PageDown |
Move tab left / right |
Ctrl+Shift+W |
Close tab |
Ctrl+Shift+R |
Rename tab |
Alt+1..Alt+9 |
Jump to tab by position |
Ctrl++ / Ctrl+= |
Increase font size (current tab) |
Ctrl+- |
Decrease font size (current tab) |
Ctrl+0 |
Reset font size |
Ctrl+Shift+K |
Clear scrollback |
Ctrl+Shift+L |
Toggle session logging for active pane |
| Binding | Action |
|---|---|
Ctrl+. |
Cycle Insert → Normal → Visual → Insert |
Ctrl+\ |
Enter Normal mode |
| Binding | Action |
|---|---|
Ctrl+W v |
Split horizontally |
Ctrl+W s |
Split vertically |
Ctrl+W a |
Auto-split (along longest dimension) |
Ctrl+W h/j/k/l |
Focus left / down / up / right |
Ctrl+W w |
Cycle focus |
Ctrl+W q |
Close pane |
Ctrl+W z |
Toggle pane zoom |
Ctrl+W p |
Enter screenshot mode |
Ctrl+Shift+←/→ |
Grow/shrink active pane horizontally |
Ctrl+Shift+↑/↓ |
Grow/shrink active pane vertically |
| drag separator | Drag the 1 px separator line to resize |
A rectangular selection overlay appears centered on the screen.
| Binding | Action |
|---|---|
← / → / ↑ / ↓ |
Move the selection |
Shift+→ / Shift+← |
Grow / shrink the right edge |
Shift+↓ / Shift+↑ |
Grow / shrink the bottom edge |
Enter / Space |
Capture and save PNG |
Esc |
Cancel |
The file is saved as mmterm-YYYYMMDDTHHMMSS.png in the directory set by [general] screenshot_dir (default ~/mmterm/shot). The full path is copied to the clipboard automatically after a successful capture.
BEL (0x07) — sent by the shell on tab-completion with multiple matches, by editors on invalid input, etc. — shows a yellow ● dot next to the mode badge in the status bar for 150 ms. A 500 ms cooldown suppresses repeated bells so rapid sequences (e.g. pressing Tab twice) only flash once.
To also flash the screen background (Terminator-style), set visual_bell = true in [general]:
[general]
visual_bell = true # default: false| Binding | Action |
|---|---|
Shift+PageUp / Shift+PageDown |
Scroll half screen |
Ctrl+Shift+Home / Ctrl+Shift+End |
Jump to top / bottom |
| Binding | Action |
|---|---|
/ |
Open search |
Enter |
Next match |
↑ / ↓ |
Navigate search history |
Ctrl+C |
Copy current match |
n / N |
Next / previous match (Normal mode) |
Escape |
Exit search |
| Binding | Action |
|---|---|
v |
Enter Visual mode |
i / Escape |
Return to Insert mode |
j / k |
Scroll down / up |
/ |
Open search |
n / N |
Next / previous search match |
Navigate freely to position the cursor, press v to set the selection anchor, then move to the end and copy.
| Binding | Action |
|---|---|
h/j/k/l or arrows |
Move cursor (scrolls viewport at boundaries) |
w / b / e |
Forward word / backward word / end of word |
0 / $ |
Start / end of line |
g / G |
Top / bottom of viewport |
v |
Set selection anchor at cursor (starts highlighting) |
o |
Swap anchor and cursor |
y / Ctrl+C |
Copy selection and exit |
Y |
Yank (copy) the entire line at cursor |
q / Escape |
Exit to Insert mode |
| Binding | Action |
|---|---|
Ctrl+Shift+C |
Copy selection |
Ctrl+Shift+V |
Paste |
main.rs (App, event loop)
├── input/ — key → Action mapping, modal state
├── pty/ — PTY fork, shell spawn, read/write
├── terminal/ — VT/ANSI parser, cell grid, scrollback
├── ui/ — binary-tree split layout, pane struct
├── renderer/ — CPU pixel rendering, glyph cache
├── config.rs — TOML load/save
└── tui_config/ — in-process config editor
See doc/SPEC.md for the full architecture and feature specification.
GPL-2.0 — see LICENSE.
