Bjorn Blomberg 6aee7ce00a Fix wide-char (emoji/CJK) column drift through the whole render chain
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>
2026-07-15 09:33:06 +02:00

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:

  1. config.toml — huvud-konfigfil, definierar paneler och keybinds
  2. panels/<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: az, 09, space, enter, esc, tab, backspace, delete, up, down, left, right, home, end, pageup, pagedown, f1f12.


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)
Description
en terminal baserad windows maneger med ssh suport
Readme 10 MiB
latest Latest
2026-07-16 00:30:36 +02:00
Languages
Rust 90.3%
Python 9.7%