Metadata-Version: 2.5
Name: tinyterminal
Version: 0.1.0
Summary: A terminal emulator in a native window: xterm.js over a standard-library PTY backend
Author-email: smm-h <smmh72@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: desktop,pty,rlsbl,terminal,terminal-emulator,wesktop,xterm
Requires-Python: >=3.11
Requires-Dist: wesktop>=0.12.0
Description-Content-Type: text/markdown

# tinyterminal

A terminal emulator in a native window. xterm.js renders the screen inside a
frameless WebKitGTK window (pywebview, through
[wesktop](https://github.com/smm-h/wesktop)); behind it one PTY and one
WebSocket per tab, with the PTY side written against the Python standard
library only.

## Install

```bash
uv tool install --python /usr/bin/python3 tinyterminal   # or: pipx install tinyterminal
tinyterminal
```

Linux with GTK 3 and WebKitGTK, and a Python that can import the
distribution's PyGObject. That is the system interpreter, so install with it
explicitly: `uv` prefers its own downloaded interpreters, which cannot import
the distribution's GTK bindings, while `pipx` uses the system one by default.
On Fedora the two packages are `python3-gobject` and `webkit2gtk4.1`; other
distributions name them differently.

## Keys

| Key | Action |
| --- | --- |
| Ctrl+Shift+T | new tab |
| Ctrl+Shift+W | close tab |
| Ctrl+PageUp / Ctrl+PageDown | previous / next tab |
| Ctrl+Shift+C / Ctrl+Shift+V | copy selection / paste |

## What it does

- **Tabs**, each its own login shell on its own PTY, titled from the OSC 0/2
  title the shell sets and falling back to the shell's name. Closing the last
  tab closes the window.
- **Light and dark**, both faces of the GNOME palette. The theme button cycles
  system, light and dark; on `system` the window follows the desktop's setting
  live.
- **The fastest renderer that works.** The page probes for a `webgl2` context
  and loads xterm's WebGL addon, falls back to the canvas addon, and shows
  which one is live at the right of the tab bar. A WebGL context loss switches
  to canvas and relabels.
- **Flow control that reaches the shell.** The server counts every byte sent
  as unacknowledged until xterm's parser has consumed it, and above 1 MiB it
  stops reading the PTY, so the kernel's PTY buffer applies back-pressure to
  the program producing the flood. Nothing is ever buffered unboundedly in the
  server.
- **Its own window chrome.** The window is frameless and transparent: the
  rounded card the page draws is the window silhouette, dragging goes to the
  compositor, and eight handles around the frame hand an interactive resize to
  it.
- **Clipboard** through `navigator.clipboard`, falling back to the desktop's
  own clipboard through GDK when the web view refuses.

## Known gaps

- **Linux and GTK 3 only in practice.** The resize handles, the clipboard
  fallback and unmaximizing all call the GTK toplevel directly. On another
  pywebview backend those do nothing; nothing else has been tried.
- **No search, no per-tab titles you can edit**, and no scrollbar styling
  beyond a thin native one.
- **`allowProposedApi: true` is on**, which xterm 6 requires for
  `Terminal.unicode` -- the Unicode 11 addon registers into it. No other
  proposed API is used.
- **Opened in a plain browser** rather than its own window, the page cannot
  close anything when the last tab exits, and the clipboard has only its web
  API path.
- **A face switch can lag in a screenshot.** Late in a long scripted session a
  capture can show the previous face's top bar over an already-switched
  terminal. The page's computed style is correct, and no repaint the page can
  force changes it: WebKitGTK is holding that region's rasterisation.

## Development

```bash
uv run python -m tinyterminal            # run it from the checkout
uv run pytest                            # the headless PTY + WebSocket suite
./scripts/fetch-assets.sh                # re-vendor the pinned xterm.js build
timeout 120 uv run python scripts/screenshot-test.py   # drives the real window
```

The vendored xterm.js build is not committed. `scripts/xterm-versions.txt`
pins the versions, `scripts/fetch-assets.sh` downloads exactly those from the
npm registry into `src/tinyterminal/static/vendor/` (curl and tar, no node),
and `hatch_build.py` runs that script during a build so the wheel carries the
engine.

## Layout

```
src/tinyterminal/server.py     PTY (openpty/fork/execvpe) + WebSocket bridge + flow control
src/tinyterminal/app.py        routes, window chrome, the js_api the page drives the window with
src/tinyterminal/static/       the frame, tab bar, themes, renderer choice, keys, clipboard
scripts/fetch-assets.sh        re-vendors the pinned engine
scripts/screenshot-test.py     windowed scene runner, writes screenshots/
tests/                         headless backend suite
bin/cli.js                     the npm shim, which runs the installed Python app
```

## License

MIT
