ttwm (Tabbed Tiling Window Manager) is a minimal X11 tiling window manager inspired by Notion. It combines the efficiency of tiling layouts with the organization of tabbed windows, allowing multiple windows to share the same screen space as tabs within frames.
- Core Concepts
- Installation
- Quick Start
- Keyboard Shortcuts
- Mouse Interactions
- Configuration
- IPC and ttwmctl
- Troubleshooting
A frame is a rectangular area on the screen that can hold one or more windows. When a frame contains multiple windows, they are displayed as tabs at the top of the frame. Only one window (the focused tab) is visible at a time; the others are hidden but remain managed.
Empty frames (created by splitting) display a bordered placeholder in the content area. The border color indicates focus state: blue when focused, gray when unfocused. Click on an empty frame to focus it, then open a new window to place it there.
Tabs are windows stacked within a single frame. Each tab shows a title in the tab bar at the top of the frame. Click a tab or use keyboard shortcuts to switch between tabs. The focused tab has a highlighted background color.
Vertical tabs can be enabled per-frame with Mod4+/. In vertical mode, tabs appear on the left side of the frame and display only the application icon (no text). This is useful for frames with many windows where you want to maximize horizontal space. Toggle back to horizontal tabs with the same shortcut.
Splits divide a frame into two smaller frames, either horizontally (side-by-side) or vertically (stacked). You can create complex layouts by splitting frames repeatedly. The gap between frames can be dragged to resize the split.
ttwm provides 9 virtual workspaces (desktops). Each workspace maintains its own independent layout tree. Cycle through workspaces to organize windows by task or project.
Floating windows are exempt from the tiling layout. They render above tiled windows and can be freely moved and resized with the mouse. Some window types automatically float:
- Dialog boxes
- Splash screens
- Toolbars and utility windows
- Menus and tooltips
You can manually toggle any window between tiled and floating mode with Mod4+f. Floating windows are per-workspace (hidden when you switch workspaces).
Fullscreen windows cover the entire screen, hiding tab bars, borders, gaps, and even dock bars (like polybar). This is true fullscreen mode.
- Toggle fullscreen with
Mod4+Enter - Applications can request fullscreen via EWMH
_NET_WM_STATE_FULLSCREEN(e.g., browser video fullscreen) - Fullscreen is per-workspace (each workspace can have its own fullscreen window)
- Pressing
Mod4+Enteragain exits fullscreen and restores the normal layout
Urgent windows are windows that request attention using the _NET_WM_STATE_DEMANDS_ATTENTION hint. This is typically triggered by:
- Terminal bell (
echo -e '\a') - Chat applications receiving messages
- Download completion notifications
- Any application requesting user attention
When a window becomes urgent:
- Its tab turns orange/amber (configurable via
tab_urgent_bg) - If the urgent window is on another workspace, a small orange indicator appears in the upper-right corner of the screen
Clearing urgent state:
- Focus the urgent window (the orange highlight clears automatically)
- Use
Mod4+Spaceto jump to the oldest urgent window
Urgent windows are handled in FIFO order (first-in, first-out), so Mod4+Space always focuses the window that has been waiting longest for attention.
ttwm supports multiple monitors with per-monitor workspaces (similar to i3/bspwm). Each monitor maintains its own independent set of 9 workspaces.
Key features:
- Each monitor has its own 9 workspaces
- Workspace switching only affects the currently focused monitor
- Use
Mod4+Control+Left/Rightto move focus between monitors - Drag tabs to frames on other monitors to move windows
- Use tagging (
Mod4+t) to batch-move windows between monitors
Monitor detection:
- Monitors are detected via RandR at startup
- RandR hotplug events are supported (connect/disconnect monitors)
- Rust toolchain (1.70 or later)
- X11 development libraries
- FreeType development libraries
On Debian/Ubuntu:
sudo apt install build-essential libx11-dev libxcb1-dev libfreetype6-devOn Arch Linux:
sudo pacman -S base-devel libx11 libxcb freetype2git clone https://github.com/adereth/ttwm.git
cd ttwm
cargo build --releaseThe binaries will be in target/release/:
ttwm- The window managerttwmctl- The control CLI tool
From a display manager: Add a desktop entry or select ttwm from your display manager's session menu.
From .xinitrc:
exec /path/to/ttwmConfiguration: Copy the example config to your home directory:
mkdir -p ~/.config/ttwm
cp config.toml.example ~/.config/ttwm/config.toml- Start ttwm and you'll see an empty screen
- Open a terminal: Press
Mod4+x(Super+x) to spawn alacritty (configurable via[exec]section) - Split the screen: Press
Mod4+sfor horizontal split orMod4+vfor vertical split - Open another terminal: The new window appears in the newly focused frame
- Navigate between frames: Use
Mod4+Left/Right/Up/Downto move focus - Create tabs: Open multiple windows in the same frame to create tabs
- Switch tabs: Use
Mod4+Page_Down/Mod4+Page_UporMod4+1-9
All keyboard shortcuts use Mod4 (the Super/Windows key) as the primary modifier. These can be customized in your config file.
| Shortcut | Action |
|---|---|
Mod4+x |
Spawn alacritty |
Mod4+r |
Run gmrun |
| Shortcut | Action |
|---|---|
Mod4+q |
Close focused window |
Mod4+f |
Toggle floating mode for focused window |
Mod4+Enter |
Toggle fullscreen mode for focused window |
Mod4+/ |
Toggle vertical tabs for focused frame |
Mod4+Control+F4 |
Quit ttwm |
| Shortcut | Action |
|---|---|
Mod4+Page_Down |
Focus next tab |
Mod4+Page_Up |
Focus previous tab |
Mod4+1 through Mod4+9 |
Focus tab by number |
| Shortcut | Action |
|---|---|
Mod4+j |
Focus next window (linear) |
Mod4+k |
Focus previous window (linear) |
| Shortcut | Action |
|---|---|
Mod4+Left |
Focus frame to the left |
Mod4+Right |
Focus frame to the right |
Mod4+Up |
Focus frame above |
Mod4+Down |
Focus frame below |
| Shortcut | Action |
|---|---|
Mod4+s |
Split current frame horizontally |
Mod4+v |
Split current frame vertically |
| Shortcut | Action |
|---|---|
Mod4+Shift+Left |
Move window to frame on left |
Mod4+Shift+Right |
Move window to frame on right |
| Shortcut | Action |
|---|---|
Mod4+Control+Left |
Shrink focused split |
Mod4+Control+Right |
Grow focused split |
| Shortcut | Action |
|---|---|
Mod4+] |
Switch to next workspace |
Mod4+[ |
Switch to previous workspace |
| Shortcut | Action |
|---|---|
Mod4+t |
Toggle tag on focused window |
Mod4+a |
Move all tagged windows to focused frame |
Mod4+Shift+t |
Untag all windows |
| Shortcut | Action |
|---|---|
Mod4+Space |
Focus oldest urgent window (jumps to other workspace if needed) |
| Shortcut | Action |
|---|---|
Mod4+Control+Left |
Focus monitor to the left |
Mod4+Control+Right |
Focus monitor to the right |
- Left-click on a tab: Focus that window
- Left-click on empty frame's tab bar: Focus the empty frame
- Left-click in empty frame: Focus the empty frame
- Left-click and drag: Resize the split by dragging the gap between frames
- Left-click inside a floating window: Focus the window
- Left-click and drag inside a floating window: Move the window
- Left-click and drag on the edge/corner of a floating window: Resize the window
Floating windows have an 8-pixel resize zone around their edges. The cursor will change to indicate resize direction.
ttwm is configured through a TOML file located at ~/.config/ttwm/config.toml.
[appearance]
# Gap between windows (pixels)
gap = 8
# Gap from screen edges (pixels)
outer_gap = 8
# Window border width (pixels)
border_width = 2
# Tab bar height (pixels)
tab_bar_height = 26
# Tab bar font (fontconfig name)
tab_font = "Segoe UI"
# Tab bar font size in points
tab_font_size = 12
# Show application icons in tabs (20x20 pixels)
show_tab_icons = true
# Vertical tab bar width (pixels) - icons only, no text
vertical_tab_width = 28All colors are specified in hex format (#RRGGBB):
[colors]
# Tab bar background (uses pseudo-transparency from desktop wallpaper)
tab_bar_bg = "#000000"
# Focused tab background
tab_focused_bg = "#5294e2"
# Unfocused tab background (same frame)
tab_unfocused_bg = "#3a3a3a"
# Visible tab in an unfocused frame
tab_visible_unfocused_bg = "#4a6a9a"
# Tagged window tab background
tab_tagged_bg = "#e06c75"
# Text color for focused tabs
tab_text = "#ffffff"
# Text color for unfocused tabs
tab_text_unfocused = "#888888"
# Separator line between inactive tabs
tab_separator = "#4a4a4a"
# Border color for focused window
border_focused = "#5294e2"
# Border color for unfocused windows
border_unfocused = "#3a3a3a"Override default keybindings in the [keybindings] section. Format: "Modifier+Key"
Available modifiers: Mod4 (Super), Shift, Control, Alt
[keybindings]
# Examples
close_window = "Mod4+Shift+c" # Change to Mod4+Shift+c
split_horizontal = "Mod4+h" # Change to Mod4+h
split_vertical = "Mod4+v" # Keep as Mod4+vAll available keybinding options:
cycle_tab_forward,cycle_tab_backwardfocus_tab_1throughfocus_tab_9focus_next,focus_prevfocus_frame_left,focus_frame_right,focus_frame_up,focus_frame_downmove_window_left,move_window_rightresize_shrink,resize_growsplit_horizontal,split_verticalclose_window,toggle_float,toggle_fullscreen,toggle_vertical_tabs,quitworkspace_next,workspace_prevtag_window,move_tagged_windows,untag_allfocus_monitor_left,focus_monitor_right
Run programs with keybindings using the [exec] section. Format: "Modifier+Key" = "command [args...]"
[exec]
# Run alacritty terminal with Mod4+x
"Mod4+x" = "alacritty"
# Run gmrun launcher with Mod4+r
"Mod4+r" = "gmrun"
# Run htop in alacritty with Mod4+Shift+x
"Mod4+Shift+x" = "alacritty -e htop"Define initial layouts and spawn applications automatically when ttwm starts using the [startup] section. Each workspace (1-9) can have its own layout tree.
Layout nodes are either:
- frame: A leaf node that contains windows (displayed as tabs)
- split: An internal node that divides space between two children
Frame options:
name: Optional frame name (for identification)vertical_tabs: Display tabs vertically on the left (default: false)apps: List of commands to spawn in this frame
Split options:
direction:"horizontal"(side-by-side) or"vertical"(stacked)ratio: Split ratio from 0.1 to 0.9 (default: 0.5)first: First child node (left for horizontal, top for vertical)second: Second child node (right for horizontal, bottom for vertical)
Example: Development workspace with 60/40 split
[startup.workspace.1]
layout = { type = "split", direction = "horizontal", ratio = 0.6, first = { type = "frame", name = "editor", apps = ["code ~/projects"] }, second = { type = "split", direction = "vertical", ratio = 0.5, first = { type = "frame", name = "terminal", apps = ["alacritty"] }, second = { type = "frame", name = "browser", apps = ["firefox"] } } }This creates:
+---------------------------+---------------+
| | terminal |
| editor +---------------+
| (60%) | browser |
| | |
+---------------------------+---------------+
Example: Simple single-frame workspace
[startup.workspace.2]
layout = { type = "frame", name = "scratch" }Example: Vertical split with vertical tabs
[startup.workspace.3]
layout = { type = "split", direction = "vertical", ratio = 0.7, first = { type = "frame", name = "main", apps = ["alacritty"] }, second = { type = "frame", name = "references", vertical_tabs = true, apps = ["firefox"] } }Notes:
- Paths support tilde expansion (e.g.,
~/projectsexpands to your home directory) - Apps are spawned after the layout is created, so they appear in their designated frames
- If an app fails to spawn, ttwm logs an error and continues with the remaining apps
- Startup layouts are applied before scanning for existing windows
ttwm provides a Unix socket IPC interface for external control and scripting. The ttwmctl command-line tool communicates with the running window manager.
The socket is created at /tmp/ttwm$DISPLAY.sock (e.g., /tmp/ttwm_0.sock for display :0).
# Get full WM state as JSON
ttwmctl state
# Get layout tree
ttwmctl layout
# List all windows
ttwmctl windows
# Get focused window ID
ttwmctl focused
# Focus a specific window by ID
ttwmctl focus 0x1c00004
# Focus tab by index (1-9)
ttwmctl focus-tab 2
# Focus frame in a direction
ttwmctl focus-frame left
# Split the focused frame
ttwmctl split horizontal
ttwmctl split vertical
# Move window to adjacent frame
ttwmctl move-window forward
ttwmctl move-window backward
# Resize the focused split
ttwmctl resize grow
ttwmctl resize shrink
# Close focused window
ttwmctl close
# Cycle tabs
ttwmctl cycle-tab forward
ttwmctl cycle-tab backward
# Tagging commands
ttwmctl tag # Tag focused window
ttwmctl tag 0x1c00004 # Tag specific window
ttwmctl untag # Untag focused window
ttwmctl toggle-tag # Toggle tag on focused window
ttwmctl move-tagged # Move all tagged to focused frame
ttwmctl untag-all # Untag all windows
ttwmctl tagged # List tagged window IDs
# Floating window commands
ttwmctl toggle-float # Toggle floating for focused window
ttwmctl toggle-float 0x1c00004 # Toggle floating for specific window
ttwmctl floating # List floating window IDs
# Fullscreen commands
ttwmctl toggle-fullscreen # Toggle fullscreen for focused window
ttwmctl toggle-fullscreen 0x1c00004 # Toggle fullscreen for specific window
ttwmctl fullscreen # Get fullscreen window ID (if any)
# Urgent window commands
ttwmctl urgent # List urgent window IDs (oldest first)
ttwmctl focus-urgent # Focus oldest urgent window
# Workspace commands
ttwmctl workspace 3 # Switch to workspace 3
ttwmctl workspace next # Switch to next workspace
ttwmctl workspace prev # Switch to previous workspace
ttwmctl current-workspace # Get current workspace number
ttwmctl move-to-workspace 2 # Move focused window to workspace 2
ttwmctl move-to-workspace 2 --window 0x1c00004 # Move specific window
# Monitor commands
ttwmctl monitors # List all monitors with geometry and state
ttwmctl current-monitor # Get currently focused monitor name
ttwmctl focus-monitor DP-1 # Focus a specific monitor by name
ttwmctl focus-monitor left # Focus monitor to the left
ttwmctl focus-monitor right # Focus monitor to the right
# Validate WM state (for debugging)
ttwmctl validate
# Get recent event log
ttwmctl event-log
# Quit the window manager
ttwmctl quitFocus a window by title pattern:
# Find and focus Firefox
window_id=$(ttwmctl windows | jq -r '.data[] | select(.title | contains("Firefox")) | .id')
ttwmctl focus "$window_id"Create a three-column layout:
ttwmctl split horizontal
ttwmctl focus-frame right
ttwmctl split horizontal- Check that no other window manager is running
- Verify X11 is running and DISPLAY is set correctly
- Check logs:
RUST_LOG=info ttwm 2>&1 | tee ttwm.log
- Ensure no other application is grabbing the same key combinations
- Check config file syntax with a TOML validator
- Verify the config file location:
~/.config/ttwm/config.toml
- Install the specified font or use a system font like "monospace"
- Try different font names: "DejaVu Sans", "Liberation Sans", "FreeSans"
- Verify ttwm is running
- Check the socket exists:
ls -la /tmp/ttwm*.sock - Ensure DISPLAY environment variable is set correctly
- Some windows request specific sizes; ttwm respects minimum size hints
- Dialog windows may float or overlap
- Check window class/type with
xpropto understand window behavior
-
Use workspaces: Keep related windows on the same workspace (e.g., browser + docs on workspace 1, terminal + editor on workspace 2)
-
Tab heavy workflows: Put related windows as tabs in the same frame rather than splitting many times
-
Quick splits: After splitting, the new empty frame is focused, so just open your next application
-
Resize with mouse: For fine-grained control, drag the gaps between frames
-
Script common layouts: Use ttwmctl to create scripts for your preferred layouts