Wide characters shifted everything after them one column to the right: - render.rs: vt100's empty wide-continuation cell was padded to a space, making wide chars occupy 2+1 columns — skip it entirely - ipc.rs buffer_to_ansi: same bug for daemon frames — skip cells covered by a preceding wide symbol (unicode-width) - app.rs: clamp virtual terminals to >= 2 columns (vt100 0.16 has a subtraction underflow panic on wide chars in a 1-col grid) and give content_area a sane 80x24 default so windows spawned before the first layout pass aren't degenerate - main.rs: use try_send for display frames so one slow/stalled client can no longer freeze the whole WM - integration test: new end-to-end check that emoji do not shift the row, plus fix a false match against the window title Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TUI-WM
En terminal-baserad fönsterhanterare med flytande fönster, full mussuport och SSH-kompatibilitet — inspirerad av tmux men med ett modernt UX.
Funktioner
- Flytande terminaler med fri positionering och storleksändring
- Full mussuport: vänster/höger/mitten-klick, scroll, drag, ANSI-muse-protokoll vidarebefordras till appar
- Panel (topp/botten) med klickbara knappar och dynamiska status-widgets
- Dropdown-menyer med undermenyer
- Körbar kommandodialog (
ctrl+space) - Dynamisk omrendering vid terminalstorlek-ändringar
- SSH-kompatibel (fungerar via vanliga ANSI escape-koder)
- Kompilerar till fristående binärer för Linux och Windows
Teknikstack
| Komponent | Bibliotek |
|---|---|
| Terminal I/O, mus, resize | crossterm |
| TUI-rendering, buffersystem | ratatui |
| Pseudoterminal (PTY/ConPTY) | portable-pty |
| VT100-emulering | vt100 |
| Konfiguration | toml + serde |
| Språk | Rust |
Bygga projektet
Förutsättningar
- Rust toolchain (
rustup,cargo) - För Windows-cross-kompilering från Linux:
mingw-w64
Bygga för Linux
cargo build --release --target x86_64-unknown-linux-gnu
Bygga för Windows
cargo build --release --target x86_64-pc-windows-gnu
Binärerna hamnar i target/<target>/release/.
Via VS Code
Använd Terminal > Run Task och välj:
- Build Linux — bygger för
x86_64-unknown-linux-gnu - Build Windows — bygger för
x86_64-pc-windows-gnu - Build (native) — bygger för det platform du sitter på
- Run — kör appen direkt
Projektstruktur
TUI-WM/
├── src/
│ ├── main.rs — Startpunkt, event-loop
│ ├── app.rs — All logik: fönsterhantering, mus, event-routing
│ ├── config.rs — TOML-konfiguration och dess datatyper
│ ├── pty.rs — PTY-instans (startar och kommunicerar med shell/app)
│ └── render.rs — Ratatui-rendering av paneler och fönster
├── config.toml — Huvud-konfigfil (panels, keybinds)
├── panels/
│ └── topbar.toml — Panel-definition (knappar, status-widgets)
├── .vscode/
│ └── tasks.json
└── Cargo.toml
Konfiguration
Konfigurationen delas upp i två nivåer:
config.toml— huvud-konfigfil, definierar paneler och keybindspanels/<fil>.toml— extern panel-fil, definierar knapparna och status-widgets för en specifik panel
Paneler
En panel är en topbar eller bottombar med knappar och status-widgets.
# config.toml
[[panel]]
position = "top" # "top" | "bottom"
file = "panels/topbar.toml" # länk till extern panel-definition (valfri)
Eller inline utan extern fil:
[[panel]]
position = "bottom"
[[panel.item]]
label = "Terminal"
action = { type = "spawn_terminal" }
Knappar (items)
Varje knapp i en panel kopplas till en action:
# panels/topbar.toml
[[item]]
label = "Terminal"
action = { type = "spawn_terminal" }
[[item]]
label = "Exit"
action = { type = "exit" }
Tillgängliga actions
| Action | Beskrivning |
|---|---|
{ type = "exit" } |
Avslutar TUI-WM |
{ type = "spawn_terminal" } |
Startar nytt terminalfönster med systemets standard-shell |
{ type = "spawn_terminal", shell = "/bin/bash" } |
Startar terminal med specifikt shell/program |
{ type = "spawn_run_dialog" } |
Öppnar körbar kommandodialog |
{ type = "run_program", command = "htop" } |
Kör ett program i terminalfönster |
{ type = "run_script", path = "/path/to/script.sh" } |
Kör ett skript i terminalfönster |
{ type = "submenu", item = [...] } |
Öppnar en dropdown-undermeny |
Dropdown-undermeny
[[item]]
label = "Apps"
action = { type = "submenu", item = [
{ label = "htop", action = { type = "run_program", command = "htop" } },
{ label = "vim", action = { type = "run_program", command = "vim" } },
] }
Status-widgets
Status-widgets är dynamiska textblock i panelen som kör ett kommando periodiskt.
# panels/topbar.toml
[[status]]
command = "date '+%H:%M:%S'" # shell-kommando vars stdout visas
interval = 1 # uppdateringsintervall i sekunder (default: 10)
width = 10 # teckenbredd i panelen
align = "right" # "right" (default) | "left"
Flera status-widgets kan definieras. Höger-dockade renderas från höger kant inåt.
Keybinds
Keybinds definieras i config.toml och kopplas till samme action-typ som knappar.
[[keybind]]
key = "ctrl+space"
scope = "global" # "global" | "wm" (default)
action = { type = "spawn_run_dialog" }
[[keybind]]
key = "alt+enter"
scope = "global"
action = { type = "spawn_terminal" }
[[keybind]]
key = "ctrl+q"
scope = "wm"
action = { type = "exit" }
Scope
| Scope | Beskrivning |
|---|---|
global |
Aktiveras alltid, även när ett terminalfönster har fokus |
wm |
Aktiveras bara när inget terminalfönster är fokuserat (default) |
Tangenter
Format: "modifier+modifier+key". Tillgängliga modifiers: ctrl, alt, shift.
Exempelnycklar: a–z, 0–9, space, enter, esc, tab, backspace, delete, up, down, left, right, home, end, pageup, pagedown, f1–f12.
Mushantering
| Åtgärd | Effekt |
|---|---|
| Vänsterklick på titel-rad | Fokuserar och börjar flytta fönstret |
Vänsterklick på [x] |
Stänger fönstret |
| Vänsterklick på kant/hörn | Börjar storleksändra fönstret |
| Klick i innehållsyta | Fokuserar fönstret och skickar musklick till appen i terminalen |
| Scroll inuti terminal | Skickar scroll-event till appen (med ANSI mus-protokoll) eller pilar som fallback |
| Höger/mitten-klick i terminal | Vidarebefordras direkt till appen |
Möss-tracking fungerar automatiskt — om appen i terminalen aktiverar ANSI mus-protokoll (t.ex. vim, htop, ncurses-appar) vidarebefordras alla mus-events korrekt med rätt encoding (SGR, UTF-8 eller default X10).
Startlägen
TUI-WM kan startas i tre lägen:
Standalone (standard)
tui-wm
Kör fönsterhanteraren i den aktuella terminalen och startar samtidigt en Unix-socket-server. Andra program kan ansluta till socketen medan TUI-WM kör.
Daemon (-d)
tui-wm -d
Kör headless — ingen lokal terminal-rendering. State hanteras enbart via socketen. Frames renderas internt och skickas till anslutna display-klienter. Passar för att köra TUI-WM som bakgrundstjänst.
Klient (-c)
tui-wm -c [socket-sökväg]
Ansluter till en befintlig TUI-WM-server (standalone eller daemon) som display-klient. Renderar mottagna frames och vidarebefordrar tangentbords-, mus- och resize-events — klick, drag, scroll och hover fungerar precis som lokalt, även över SSH. Om socket-sökväg utelämnas används standard-sökvägen. Koppla ned med Ctrl+Shift+Q.
Tester
cargo test # enhetstester (IPC-protokollet)
python3 tests/integration.py # end-to-end: daemon + frames + musforwarding
Socket-API
Appar som körs inne i TUI-WM får miljövariablerna TUI_WM_SOCKET (sökväg till socketen) och TUI_WM_WINDOW_ID (fönstrets ID) satta automatiskt. Via socketen kan de:
- Spawna nya terminalfönster
- Visa popup-dialoger med knappar och vänta på svar
- Lista eller stänga fönster
- Ta emot renderade frames (ANSI) för att visa TUI-WM:s skärm
Se SOCKET_API.md för fullständig protokolldokumentation med JSON-exempel och kodexempel i Bash och Python.
Standardtangenter
| Tangent | Funktion |
|---|---|
ctrl+space |
Öppna körbar kommandodialog |
alt+enter |
Nytt terminalfönster |
ctrl+q |
Avsluta (bara när inget terminalfönster fokuseras) |