Repository navigation
Terminal
An xterm.js terminal connected to a WebSocket shell relay. IguanaXterm is built on it.
from wapyt import Terminal, TerminalConfig, TerminalTheme
term = Terminal(TerminalConfig(
ws_url=f"/ws/terminal/{session_id}", # resolved against the page origin (ws/wss follows http/https)
asset_base="/xterm",
fit_debounce_ms=120,
theme=TerminalTheme(background="#0b1220"),
), container=tabs.get_cell(f"term_{session_id}"))
term.on_disconnect(lambda p: show_status(p["code"]))xterm is not bundled with wapyt: it is about 300 KB that only terminal apps
need, and pyTincture's CSP (script-src 'self') rules out a CDN. Serve
xterm.js, xterm.css, addon-fit.js and, for search, addon-search.js from
your own origin under asset_base (default /xterm).
JSON text frames over the WebSocket:
| Direction | Frame |
|---|---|
| browser → relay | {"type": "input", "data": "<keystrokes>"} |
| browser → relay | {"type": "resize", "cols": 120, "rows": 32} |
| relay → browser | {"type": "output", "data": "<bytes for the screen>"} |
| relay → browser |
{"type": "connected", "data": "<host>"} → on_connect
|
| relay → browser | {"type": "disconnected", "data": "<reason>"} |
| relay → browser |
{"type": "error", "data": "<message>"} → on_error
|
A frame that is not JSON is written to the screen as is.
Terminal bytes never cross into Python. Keystrokes and output go straight between xterm and the socket in JavaScript. Pyodide runs on the main thread, and a Python hop per keypress and per output frame is what makes browser terminals feel laggy. The Python wrapper only manages the lifecycle.
TerminalConfig |
|
|---|---|
ws_url |
Relay endpoint; omit to connect later with connect()
|
font_family, font_size, scrollback, cursor_blink, theme
|
Appearance (TerminalTheme(background, foreground, cursor, cursor_accent, selection_background)) |
search |
Search addon, Ctrl+F |
clipboard |
Windows Terminal-style copy/paste and a right-click ContextMenu (Copy, disabled without a selection; Paste; Select all); costs sending a literal Ctrl+V |
reconnect, reconnect_max_attempts, reconnect_base_ms
|
Exponential backoff |
fit_debounce_ms |
Set ~120 when the pane can be drag-resized, so a drag does not send a PTY resize per frame |
on_ready, on_connect ({host}), on_disconnect ({code, clean}),
on_error, on_reconnecting ({attempt, delay}), on_reconnect_failed,
on_title ({title}, OSC 0/2), on_copy ({chars}), on_clipboard_error
({action, message}).
connect, disconnect, reconnect, fit, focus, copy_selection,
paste_clipboard, write (local notices), clear, show_search,
hide_search, toggle_search, destroy.
Call fit() when a hidden tab becomes visible: a hidden pane has no size, and
the widget skips fitting while hidden.
wapyt is MIT-licensed · Repository · pyTincture · Issues
Getting started
Widgets
- Layout and MainWindow
- Tree
- DataTable
- Form
- ModalWindow
- Window
- TabWidget
- Toolbar
- Sidebar
- Chat
- Terminal
- CardPanel
- ResourceBoard
- File Transfer
- Message
- ContextMenu
- MenuBar
- Listbox
- Kanban
- ProgressBar
- Pagination
- Popup and tooltips
- Chart
- Scheduler
- MediaPlayer
Guides
Planning
Contributing