tappty 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tappty-0.1.0/CHANGELOG.md +56 -0
- tappty-0.1.0/LICENSE +21 -0
- tappty-0.1.0/MANIFEST.in +8 -0
- tappty-0.1.0/PKG-INFO +224 -0
- tappty-0.1.0/README.md +174 -0
- tappty-0.1.0/demos/README.md +41 -0
- tappty-0.1.0/demos/color_chart.py +79 -0
- tappty-0.1.0/demos/drive_vim.py +111 -0
- tappty-0.1.0/demos/matrix_rain.py +78 -0
- tappty-0.1.0/demos/mission_control.py +122 -0
- tappty-0.1.0/demos/recordings/cbonsai.cast +197 -0
- tappty-0.1.0/demos/recordings/drive_vim.cast +16 -0
- tappty-0.1.0/demos/recordings/nyancat.cast +42 -0
- tappty-0.1.0/demos/web_demo.py +106 -0
- tappty-0.1.0/docs/DESIGN.md +733 -0
- tappty-0.1.0/docs/GALLERY.md +118 -0
- tappty-0.1.0/docs/LICENSE.md +28 -0
- tappty-0.1.0/docs/README.md +21 -0
- tappty-0.1.0/docs/REFERENCE.md +702 -0
- tappty-0.1.0/docs/TAPTERM.md +512 -0
- tappty-0.1.0/docs/media/cbonsai.png +0 -0
- tappty-0.1.0/docs/media/color_chart.png +0 -0
- tappty-0.1.0/docs/media/drive_vim.mp4 +0 -0
- tappty-0.1.0/docs/media/drive_vim.png +0 -0
- tappty-0.1.0/docs/media/matrix.mp4 +0 -0
- tappty-0.1.0/docs/media/matrix_rain.png +0 -0
- tappty-0.1.0/docs/media/mission_control.png +0 -0
- tappty-0.1.0/docs/media/nyancat.gif +0 -0
- tappty-0.1.0/docs/media/nyancat.png +0 -0
- tappty-0.1.0/docs/media/web_demo.png +0 -0
- tappty-0.1.0/examples/README.md +28 -0
- tappty-0.1.0/examples/bus_capture.py +45 -0
- tappty-0.1.0/examples/custom_source.py +58 -0
- tappty-0.1.0/examples/observe_tap.py +41 -0
- tappty-0.1.0/examples/watch_and_drive.py +86 -0
- tappty-0.1.0/pyproject.toml +74 -0
- tappty-0.1.0/setup.cfg +4 -0
- tappty-0.1.0/src/tappty/__init__.py +88 -0
- tappty-0.1.0/src/tappty/arcade_ui.py +339 -0
- tappty-0.1.0/src/tappty/bus.py +520 -0
- tappty-0.1.0/src/tappty/cli.py +534 -0
- tappty-0.1.0/src/tappty/compositor.py +425 -0
- tappty-0.1.0/src/tappty/curses_ui.py +289 -0
- tappty-0.1.0/src/tappty/keys.py +56 -0
- tappty-0.1.0/src/tappty/pygame_ui.py +180 -0
- tappty-0.1.0/src/tappty/pyte_terminal.py +156 -0
- tappty-0.1.0/src/tappty/recorder.py +203 -0
- tappty-0.1.0/src/tappty/session.py +334 -0
- tappty-0.1.0/src/tappty/source.py +717 -0
- tappty-0.1.0/src/tappty/style.py +141 -0
- tappty-0.1.0/src/tappty/terminal.py +144 -0
- tappty-0.1.0/src/tappty/video.py +221 -0
- tappty-0.1.0/src/tappty/web_ui.py +299 -0
- tappty-0.1.0/src/tappty.egg-info/PKG-INFO +224 -0
- tappty-0.1.0/src/tappty.egg-info/SOURCES.txt +85 -0
- tappty-0.1.0/src/tappty.egg-info/dependency_links.txt +1 -0
- tappty-0.1.0/src/tappty.egg-info/entry_points.txt +2 -0
- tappty-0.1.0/src/tappty.egg-info/requires.txt +25 -0
- tappty-0.1.0/src/tappty.egg-info/top_level.txt +1 -0
- tappty-0.1.0/tests/test_arcade_smoke.py +74 -0
- tappty-0.1.0/tests/test_bus_cmd.py +68 -0
- tappty-0.1.0/tests/test_bus_security.py +179 -0
- tappty-0.1.0/tests/test_bus_socket.py +288 -0
- tappty-0.1.0/tests/test_bus_tcp.py +38 -0
- tappty-0.1.0/tests/test_cast_source.py +146 -0
- tappty-0.1.0/tests/test_cli.py +84 -0
- tappty-0.1.0/tests/test_compositor_backings.py +71 -0
- tappty-0.1.0/tests/test_compositor_view.py +25 -0
- tappty-0.1.0/tests/test_curses_viewport.py +91 -0
- tappty-0.1.0/tests/test_demos_smoke.py +70 -0
- tappty-0.1.0/tests/test_error_handling.py +244 -0
- tappty-0.1.0/tests/test_examples.py +44 -0
- tappty-0.1.0/tests/test_gui_smoke.py +133 -0
- tappty-0.1.0/tests/test_keys.py +23 -0
- tappty-0.1.0/tests/test_pipe_source.py +46 -0
- tappty-0.1.0/tests/test_pty_source.py +43 -0
- tappty-0.1.0/tests/test_pyte_terminal.py +123 -0
- tappty-0.1.0/tests/test_recorder.py +201 -0
- tappty-0.1.0/tests/test_session_bus.py +69 -0
- tappty-0.1.0/tests/test_session_echo.py +20 -0
- tappty-0.1.0/tests/test_source_encoding.py +63 -0
- tappty-0.1.0/tests/test_style.py +90 -0
- tappty-0.1.0/tests/test_talking_stick.py +122 -0
- tappty-0.1.0/tests/test_term.py +80 -0
- tappty-0.1.0/tests/test_video.py +57 -0
- tappty-0.1.0/tests/test_video_bounds.py +40 -0
- tappty-0.1.0/tests/test_web_ui.py +241 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2026-06-17 — And so it begins...
|
|
4
|
+
|
|
5
|
+
- **13:10** — Started: the fixed-size VT52 `Terminal` grid
|
|
6
|
+
- **14:29** — the first pygame renderer
|
|
7
|
+
- **22:51** — First end-to-end run of observe/control core
|
|
8
|
+
|
|
9
|
+
## 2026-06-18 — standalone
|
|
10
|
+
|
|
11
|
+
- **23:30** — Extracted to a standalone package
|
|
12
|
+
- **23:35** — round-tripped a real subprocess through the pty into the grid.
|
|
13
|
+
|
|
14
|
+
## 2026-06-19 — renderers, color, bus, packaging
|
|
15
|
+
|
|
16
|
+
- **10:54** — Runtime hardening
|
|
17
|
+
- **11:06** — Consolidated the byte-source reader loop into `Source._pump`.
|
|
18
|
+
- **12:08** — The detailed docs: `DESIGN.md`, `REFERENCE.md`, the `tapterm` guide.
|
|
19
|
+
- **13:14** — Windows: the `win` extra bundles `windows-curses` (curses CUI on Windows).
|
|
20
|
+
- **13:32** — `arcade_ui` renderer — the pyglet/OpenGL twin of `pygame_ui`.
|
|
21
|
+
- **14:23** — SGR **color** in the GUI, then in the curses CUI too.
|
|
22
|
+
- **14:55** — Raw-mode full-screen TUI input (`--raw`), so vim/htop work.
|
|
23
|
+
- **15:30** — `web_ui` renderer — the terminal in a browser over a websocket.
|
|
24
|
+
- **20:33** — Bold/italic/underline as real attributes; then strikethrough and blink.
|
|
25
|
+
- **20:46** — Packaging: `MANIFEST.in` + a validated (non-publishing) build.
|
|
26
|
+
- **21:10** — SGR color over the bus (styled snapshot + colored compositor panels).
|
|
27
|
+
- **21:37** — Wide CJK and single-code-point emoji at their true two columns.
|
|
28
|
+
- **22:44** — A GitHub Pages docs site: `docs/` sources, a `gh-pages/` builder, Actions deploy.
|
|
29
|
+
- **23:05** — Published a `tapterm` alias distribution alongside `tappty`.
|
|
30
|
+
- **23:50** — Docs-site nav and hero polish.
|
|
31
|
+
|
|
32
|
+
## 2026-06-20 — recording formats, video, demos, the terminal
|
|
33
|
+
|
|
34
|
+
- **10:52** — A **gallery** of runnable demos (screenshots + source).
|
|
35
|
+
- **11:15** — `.ttyrec` read/write, and recording any live session to `.cast`/`.ttyrec`
|
|
36
|
+
- **11:47** — Host & record real ANSI programs; bundle `nyancat`/`cbonsai` recordings
|
|
37
|
+
- **12:01** — Play and export ANSI/BBS art `.ans` (CP437 + SAUCE) and animated `.3a`
|
|
38
|
+
- **12:27** — **Render to video** — any recording to mp4/webm/gif via ffmpeg
|
|
39
|
+
- **12:41** — render of a live command (record + render in one step).
|
|
40
|
+
- **13:14** — Split `demos/` (runnable showpieces) from `examples/` (API coding examples).
|
|
41
|
+
- **13:50** — Gallery goes visual: an embedded movie, a looping GIF, a digital-rain mp4.
|
|
42
|
+
- **14:06** — `tapterm` is a **regular terminal** by default
|
|
43
|
+
- **14:12** — `tapterm` takes xterm like arguments where useful
|
|
44
|
+
- **14:26** — Observe-and-control demos: an autopilot driving live `vim` (open loop)
|
|
45
|
+
- **14:35** — reactive bot demo that reads the stream and decides what to type (closed loop).
|
|
46
|
+
- **14:55** — A web-renderer demo with a real-browser (Playwright) screenshot.
|
|
47
|
+
- **15:42** — Named the GUI extras by graphics layer — `sdl` (pygame-ce) and `gl` (arcade).
|
|
48
|
+
- **16:40** — Docs-site polish
|
|
49
|
+
- **17:28** — Runtime hardening
|
|
50
|
+
- **17:33** — Fixed race conditions
|
|
51
|
+
- **17:40** — pypy packaging prep
|
|
52
|
+
- **18:11** — Bus/control hardening
|
|
53
|
+
- **18:16** — Improve replay/render robustness
|
|
54
|
+
- **18:19** — The CUI honors `--exit-when-done` / `-hold`
|
|
55
|
+
- **18:28** — Applied `ruff format`
|
|
56
|
+
- **18:29** — Across the tree; CI installs the `web` extra so the WebSocket/CSWSH tests run.
|
tappty-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nicholas J. Kisseberth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tappty-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Files to include in the source distribution (sdist) beyond the defaults
|
|
2
|
+
# (pyproject.toml, README.md, and the LICENSE are included automatically).
|
|
3
|
+
include LICENSE CHANGELOG.md
|
|
4
|
+
recursive-include docs *.md
|
|
5
|
+
recursive-include docs/media *.png *.gif *.mp4
|
|
6
|
+
recursive-include demos *.py *.md *.cast
|
|
7
|
+
recursive-include examples *.py *.md
|
|
8
|
+
recursive-include tests *.py
|
tappty-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tappty
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Host a program on a pseudo-terminal, then observe/control/render it (CUI or GUI)
|
|
5
|
+
Author: Nicholas J. Kisseberth
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nyxcraft/tappty
|
|
8
|
+
Project-URL: Documentation, https://nyxcraft.github.io/tappty/
|
|
9
|
+
Project-URL: Source, https://github.com/nyxcraft/tappty
|
|
10
|
+
Project-URL: Changelog, https://github.com/nyxcraft/tappty/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: terminal,pty,curses,pygame,tui,vt52,emulator,instrumentation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console :: Curses
|
|
14
|
+
Classifier: Environment :: X11 Applications
|
|
15
|
+
Classifier: Environment :: Web Environment
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: System Administrators
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: POSIX
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Provides-Extra: sdl
|
|
34
|
+
Requires-Dist: pygame-ce; extra == "sdl"
|
|
35
|
+
Provides-Extra: gl
|
|
36
|
+
Requires-Dist: arcade; extra == "gl"
|
|
37
|
+
Provides-Extra: web
|
|
38
|
+
Requires-Dist: websockets; extra == "web"
|
|
39
|
+
Provides-Extra: video
|
|
40
|
+
Requires-Dist: imageio-ffmpeg; extra == "video"
|
|
41
|
+
Provides-Extra: ansi
|
|
42
|
+
Requires-Dist: pyte; extra == "ansi"
|
|
43
|
+
Provides-Extra: win
|
|
44
|
+
Requires-Dist: pywinpty; platform_system == "Windows" and extra == "win"
|
|
45
|
+
Requires-Dist: windows-curses; platform_system == "Windows" and extra == "win"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest; extra == "dev"
|
|
48
|
+
Requires-Dist: ruff; extra == "dev"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# tappty
|
|
52
|
+
|
|
53
|
+
**Host a program on a pseudo-terminal, then observe, control, and render it** — in a
|
|
54
|
+
plain terminal (curses, CUI) or a green-phosphor window (GUI).
|
|
55
|
+
|
|
56
|
+
Software Architecture, Design & Engineering by Nicholas J. Kisseberth.
|
|
57
|
+
Code Synthesized via Anthropic Claude Code / Opus 4.8.
|
|
58
|
+
Code Review by OpenAI Codex / ChatGPT 5.5.
|
|
59
|
+
|
|
60
|
+
`tappty` is a small instrumented-terminal toolkit. A program's bytes (a subprocess on a
|
|
61
|
+
PTY, or any in-process runner) flow into a fixed-size character `Terminal`; a `Session`
|
|
62
|
+
fans that output out to any number of observers and routes input back. A renderer is just
|
|
63
|
+
one more observer/controller — which is what lets a human *and* an automated client watch
|
|
64
|
+
and drive the very same session. Several sessions tile into one window via the compositor.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pip install tappty # core (CUI works out of the box)
|
|
70
|
+
pip install 'tappty[sdl]' # add the SDL/GUI window (installs pygame-ce)
|
|
71
|
+
pip install 'tappty[gl]' # add the OpenGL window (arcade; an alternative GUI backend)
|
|
72
|
+
pip install 'tappty[web]' # add the browser renderer for --web (websockets)
|
|
73
|
+
pip install 'tappty[video]' # render recordings to mp4/gif via --render (bundles ffmpeg)
|
|
74
|
+
pip install 'tappty[ansi]' # add the full-ANSI/VT100+ backend (pyte) for --ansi
|
|
75
|
+
pip install 'tappty[win]' # Windows: add the ConPTY source (pywinpty)
|
|
76
|
+
# from a checkout:
|
|
77
|
+
pip install -e '.[sdl,ansi,dev]'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`pip install tapterm` works too — it's a convenience alias that just pulls in `tappty` (which
|
|
81
|
+
provides the `tapterm` command). The library, the extras, and the docs all live under `tappty`.
|
|
82
|
+
|
|
83
|
+
## The `tapterm` program
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
tapterm # a regular terminal: your $SHELL, full-ANSI + raw keys
|
|
87
|
+
tapterm -e vim file # run a command instead of the shell, xterm-style (or: -- vim file)
|
|
88
|
+
tapterm -geometry 100x30 # xterm-style size; -T/-title sets the title, -cd DIR the working dir
|
|
89
|
+
tapterm --cui -- bash # force the curses character UI (takes over this terminal)
|
|
90
|
+
tapterm --gui -- bash # force the SDL green-phosphor window (the 'sdl' extra)
|
|
91
|
+
tapterm --arcade -- bash # same, on the arcade/OpenGL stack (the 'gl' extra)
|
|
92
|
+
tapterm --web -- bash # serve it in a browser (the 'web' extra); open http://127.0.0.1:8023/
|
|
93
|
+
tapterm --headless -- ls # run to completion, print the final screen (scripting/CI)
|
|
94
|
+
tapterm --cooked -- bash # line-oriented instrument mode (local echo on the VT52 grid)
|
|
95
|
+
tapterm --play rec.cast # replay a .cast / .ttyrec / .ans / .3a recording (--speed N, --loop)
|
|
96
|
+
tapterm --record out.cast -- bash # record a session as you use it
|
|
97
|
+
tapterm --play rec.cast --render rec.mp4 # render a recording to a video (mp4/webm/gif)
|
|
98
|
+
tapterm --render rain.mp4 --seconds 5 -- cmatrix # render a live program straight to video
|
|
99
|
+
tapterm --no-pty -- ls # host over plain pipes, no pty (cross-platform, incl. Windows)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
An interactive session behaves like a **real terminal** — full-ANSI rendering (the `ansi` extra,
|
|
103
|
+
pyte) plus raw keys, so colors, line-editing, arrows, and full-screen apps work, and the window
|
|
104
|
+
closes when the program exits, like xterm. Pass `--cooked` for the line-oriented instrument
|
|
105
|
+
default instead (local echo on the dependency-free VT52 grid) — what the observe taps and the bus
|
|
106
|
+
`CMD` capture expect. xterm-style flags are accepted where they fit: `-e`, `-T`/`-title`,
|
|
107
|
+
`-geometry`, `-cd`, `-hold`.
|
|
108
|
+
|
|
109
|
+
`--cui` works anywhere; `--gui` needs the `sdl` extra. With no mode flag, `tapterm` picks GUI when
|
|
110
|
+
the `sdl` extra is installed *and a display is available* (else CUI) — so it won't try to open a
|
|
111
|
+
window over SSH/cron. On Windows the pty path uses ConPTY (the `win` extra), which emits VT100+.
|
|
112
|
+
|
|
113
|
+
Every flag, the modes, recordings, snapshots, recipes, and troubleshooting are in the
|
|
114
|
+
[tapterm user's guide](docs/TAPTERM.md).
|
|
115
|
+
|
|
116
|
+
## Library
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from tappty import Session, Terminal, PtySource, curses_ui
|
|
120
|
+
|
|
121
|
+
sess = Session(Terminal(cols=80, rows=24))
|
|
122
|
+
sess.source = PtySource(["bash"])
|
|
123
|
+
sess.claim_control("local", "human")
|
|
124
|
+
curses_ui.run(sess, None, title="bash")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Full API — classes, signatures, the observe/control contract, and worked examples — is in
|
|
128
|
+
[docs/REFERENCE.md](docs/REFERENCE.md).
|
|
129
|
+
|
|
130
|
+
The pieces:
|
|
131
|
+
|
|
132
|
+
- **`Terminal` / `PyteTerminal`** — the screen model. `Terminal` is a fixed-size character
|
|
133
|
+
grid (VT52 spirit: wrap/scroll, the common control chars, a handful of VT52 escapes), with
|
|
134
|
+
scrollback and no deps. `PyteTerminal` is a drop-in full-ANSI/VT100+ backend (wraps `pyte`,
|
|
135
|
+
the `ansi` extra) for programs that speak modern ANSI; same read interface (plus a `cells()`
|
|
136
|
+
view of per-cell SGR color), so the GUI renderers show color while the rest is unchanged.
|
|
137
|
+
- **`Source` / `PtySource` / `EngineSource` / `CastSource` / `TtyrecSource` / `AnsSource` /
|
|
138
|
+
`ThreeASource` / `PipeSource` / `ConPtySource`** — byte producers. `PtySource` runs an external
|
|
139
|
+
command on a real pty (POSIX); `EngineSource` wraps any in-process `runner(emit, readline)`
|
|
140
|
+
callable; `CastSource` / `TtyrecSource` replay a recorded `.cast` / `.ttyrec` session, and
|
|
141
|
+
`AnsSource` / `ThreeASource` play `.ans` / `.3a` art, through the same pipeline (original
|
|
142
|
+
timing, `speed`/`loop`; `replay_source(path)` picks by extension); `PipeSource` hosts a command
|
|
143
|
+
over plain pipes (no pty, any OS); `ConPtySource` hosts one on a Windows pseudo-console (ConPTY,
|
|
144
|
+
the `win` extra).
|
|
145
|
+
- **`Session`** — hosts a Source, drives the Terminal, and exposes **observe taps**
|
|
146
|
+
(`on_stream`, `on_frame`, `on_event`) and **control** (`send_input`, `feed_key`) plus a
|
|
147
|
+
talking-stick arbitration so exactly one controller types at a time.
|
|
148
|
+
- **`Recorder` / `render_video`** — `Recorder` writes the session's output stream to a `.cast`
|
|
149
|
+
or `.ttyrec` recording as it runs (the inverse of the replay sources; `tapterm --record`);
|
|
150
|
+
`render_video` encodes a recording to a real video file (mp4/webm/gif) via ffmpeg, with
|
|
151
|
+
size/zoom/font/speed and an area-of-interest crop (`tapterm --play X --render out.mp4`).
|
|
152
|
+
- **`BusServer` / `BusClient`** — the same observe/control contract over a Unix-domain
|
|
153
|
+
socket *or* TCP (a `(host, port)` tuple — works on Windows too), so an out-of-process
|
|
154
|
+
client (a logger, an automated driver, a remote renderer) can attach to a session. It's a
|
|
155
|
+
terminal control plane — **trusted-local**: the Unix socket is owner-only, TCP is
|
|
156
|
+
loopback-only unless `allow_remote=True`, and a `token=` adds an optional shared-secret
|
|
157
|
+
gate. Not a substitute for a tunnel on an untrusted network (no TLS).
|
|
158
|
+
- **`curses_ui` / `pygame_ui` / `arcade_ui` / `web_ui`** — renderers; each exposes
|
|
159
|
+
`run(session, runner, title=…)`. `pygame_ui` and `arcade_ui` are two backends for the same
|
|
160
|
+
green-phosphor window (SDL, or arcade/OpenGL); `web_ui` serves it in a browser over a
|
|
161
|
+
WebSocket (the `web` extra).
|
|
162
|
+
- **`compositor`** — tile several panels (`SessionBacking` for in-process, `BusBacking`
|
|
163
|
+
for remote) in one GUI window, with per-tile pan/zoom and focus.
|
|
164
|
+
|
|
165
|
+
## Demos & examples
|
|
166
|
+
|
|
167
|
+
`demos/` holds runnable showpieces — the SGR color chart, the green digital rain, the compositor
|
|
168
|
+
"mission control", `drive_vim` (a program driving a live `vim` over the control tap), and
|
|
169
|
+
`web_demo` (the browser renderer). `examples/` holds short, commented API examples — the observe
|
|
170
|
+
taps, writing a custom `Source`, driving a session over the bus, and `watch_and_drive` (a bot that
|
|
171
|
+
reads the output stream and types its decisions back). The [gallery](docs/GALLERY.md) has
|
|
172
|
+
screenshots and a clip of each.
|
|
173
|
+
|
|
174
|
+
## Platform
|
|
175
|
+
|
|
176
|
+
The core (Terminal/Session/taps/talking-stick), `EngineSource`, `CastSource`, `PipeSource`,
|
|
177
|
+
the renderers, and the TCP bus are cross-platform. `PtySource` uses `pty`/`termios`
|
|
178
|
+
(POSIX-only); on Windows, host via `ConPtySource` (the `win` extra, ConPTY — paired with
|
|
179
|
+
`--ansi`) or `PipeSource` (`--no-pty`), and use the TCP bus rather than a Unix socket. The
|
|
180
|
+
Windows ConPTY path is implemented but not yet exercised on real Windows — finishing it is
|
|
181
|
+
open work (see [docs/DESIGN.md](docs/DESIGN.md) §11). The GUI needs a display; the CUI needs a
|
|
182
|
+
terminal (and, on Windows, the `win` extra's `windows-curses`); `--headless` needs neither.
|
|
183
|
+
|
|
184
|
+
## Documentation
|
|
185
|
+
|
|
186
|
+
The rendered docs site is at **[nyxcraft.github.io/tappty](https://nyxcraft.github.io/tappty/)**
|
|
187
|
+
(built from `docs/` by `gh-pages/build_site.py` and published via GitHub Actions). The sources:
|
|
188
|
+
|
|
189
|
+
- **[docs/TAPTERM.md](docs/TAPTERM.md)** — the `tapterm` command in depth: every flag,
|
|
190
|
+
the CUI / GUI / headless modes, the terminal model, recordings, snapshots, recipes, and
|
|
191
|
+
troubleshooting. For *using* tapterm.
|
|
192
|
+
- **[docs/REFERENCE.md](docs/REFERENCE.md)** — the programming/API reference: every public
|
|
193
|
+
class and method with signatures, the observe/control contracts (snapshot dict, events,
|
|
194
|
+
roles), and worked examples. For *building on* the library.
|
|
195
|
+
- **[docs/DESIGN.md](docs/DESIGN.md)** — the architecture: the Source → Terminal → Session →
|
|
196
|
+
renderer/bus pipeline, the concurrency and security/trust models, and the design rationale.
|
|
197
|
+
For *modifying* tappty.
|
|
198
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — dated history, newest first: when tappty started, when
|
|
199
|
+
it first worked, and what's changed since. The remaining open work (publish to PyPI; verify
|
|
200
|
+
Windows) is in [docs/DESIGN.md](docs/DESIGN.md) §11.
|
|
201
|
+
|
|
202
|
+
## Tests & tooling
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
pip install -e '.[dev]' # quick: core suite (pyte + GUI tests skip)
|
|
206
|
+
pytest
|
|
207
|
+
|
|
208
|
+
pip install -e '.[dev,ansi,sdl]' # full: also the ANSI backend + headless GUI smoke
|
|
209
|
+
pytest
|
|
210
|
+
|
|
211
|
+
ruff check src tests # lint (E,F,W,I,B,UP); must be clean
|
|
212
|
+
ruff format src tests # format (line-length 99, black-style)
|
|
213
|
+
|
|
214
|
+
# no install needed, straight from a checkout:
|
|
215
|
+
PYTHONPATH=src python3 -m tappty.cli --headless -- echo hello
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Without the `ansi`/`sdl` extras, `pytest` skips the pyte and GUI tests (so it reports
|
|
219
|
+
fewer passes plus a couple of skips); install `.[dev,ansi,sdl]` to run the whole suite. CI
|
|
220
|
+
runs ruff + the full matrix on Python 3.9–3.13.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT © Nicholas J. Kisseberth. See [LICENSE](LICENSE).
|
tappty-0.1.0/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# tappty
|
|
2
|
+
|
|
3
|
+
**Host a program on a pseudo-terminal, then observe, control, and render it** — in a
|
|
4
|
+
plain terminal (curses, CUI) or a green-phosphor window (GUI).
|
|
5
|
+
|
|
6
|
+
Software Architecture, Design & Engineering by Nicholas J. Kisseberth.
|
|
7
|
+
Code Synthesized via Anthropic Claude Code / Opus 4.8.
|
|
8
|
+
Code Review by OpenAI Codex / ChatGPT 5.5.
|
|
9
|
+
|
|
10
|
+
`tappty` is a small instrumented-terminal toolkit. A program's bytes (a subprocess on a
|
|
11
|
+
PTY, or any in-process runner) flow into a fixed-size character `Terminal`; a `Session`
|
|
12
|
+
fans that output out to any number of observers and routes input back. A renderer is just
|
|
13
|
+
one more observer/controller — which is what lets a human *and* an automated client watch
|
|
14
|
+
and drive the very same session. Several sessions tile into one window via the compositor.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pip install tappty # core (CUI works out of the box)
|
|
20
|
+
pip install 'tappty[sdl]' # add the SDL/GUI window (installs pygame-ce)
|
|
21
|
+
pip install 'tappty[gl]' # add the OpenGL window (arcade; an alternative GUI backend)
|
|
22
|
+
pip install 'tappty[web]' # add the browser renderer for --web (websockets)
|
|
23
|
+
pip install 'tappty[video]' # render recordings to mp4/gif via --render (bundles ffmpeg)
|
|
24
|
+
pip install 'tappty[ansi]' # add the full-ANSI/VT100+ backend (pyte) for --ansi
|
|
25
|
+
pip install 'tappty[win]' # Windows: add the ConPTY source (pywinpty)
|
|
26
|
+
# from a checkout:
|
|
27
|
+
pip install -e '.[sdl,ansi,dev]'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`pip install tapterm` works too — it's a convenience alias that just pulls in `tappty` (which
|
|
31
|
+
provides the `tapterm` command). The library, the extras, and the docs all live under `tappty`.
|
|
32
|
+
|
|
33
|
+
## The `tapterm` program
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
tapterm # a regular terminal: your $SHELL, full-ANSI + raw keys
|
|
37
|
+
tapterm -e vim file # run a command instead of the shell, xterm-style (or: -- vim file)
|
|
38
|
+
tapterm -geometry 100x30 # xterm-style size; -T/-title sets the title, -cd DIR the working dir
|
|
39
|
+
tapterm --cui -- bash # force the curses character UI (takes over this terminal)
|
|
40
|
+
tapterm --gui -- bash # force the SDL green-phosphor window (the 'sdl' extra)
|
|
41
|
+
tapterm --arcade -- bash # same, on the arcade/OpenGL stack (the 'gl' extra)
|
|
42
|
+
tapterm --web -- bash # serve it in a browser (the 'web' extra); open http://127.0.0.1:8023/
|
|
43
|
+
tapterm --headless -- ls # run to completion, print the final screen (scripting/CI)
|
|
44
|
+
tapterm --cooked -- bash # line-oriented instrument mode (local echo on the VT52 grid)
|
|
45
|
+
tapterm --play rec.cast # replay a .cast / .ttyrec / .ans / .3a recording (--speed N, --loop)
|
|
46
|
+
tapterm --record out.cast -- bash # record a session as you use it
|
|
47
|
+
tapterm --play rec.cast --render rec.mp4 # render a recording to a video (mp4/webm/gif)
|
|
48
|
+
tapterm --render rain.mp4 --seconds 5 -- cmatrix # render a live program straight to video
|
|
49
|
+
tapterm --no-pty -- ls # host over plain pipes, no pty (cross-platform, incl. Windows)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
An interactive session behaves like a **real terminal** — full-ANSI rendering (the `ansi` extra,
|
|
53
|
+
pyte) plus raw keys, so colors, line-editing, arrows, and full-screen apps work, and the window
|
|
54
|
+
closes when the program exits, like xterm. Pass `--cooked` for the line-oriented instrument
|
|
55
|
+
default instead (local echo on the dependency-free VT52 grid) — what the observe taps and the bus
|
|
56
|
+
`CMD` capture expect. xterm-style flags are accepted where they fit: `-e`, `-T`/`-title`,
|
|
57
|
+
`-geometry`, `-cd`, `-hold`.
|
|
58
|
+
|
|
59
|
+
`--cui` works anywhere; `--gui` needs the `sdl` extra. With no mode flag, `tapterm` picks GUI when
|
|
60
|
+
the `sdl` extra is installed *and a display is available* (else CUI) — so it won't try to open a
|
|
61
|
+
window over SSH/cron. On Windows the pty path uses ConPTY (the `win` extra), which emits VT100+.
|
|
62
|
+
|
|
63
|
+
Every flag, the modes, recordings, snapshots, recipes, and troubleshooting are in the
|
|
64
|
+
[tapterm user's guide](docs/TAPTERM.md).
|
|
65
|
+
|
|
66
|
+
## Library
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from tappty import Session, Terminal, PtySource, curses_ui
|
|
70
|
+
|
|
71
|
+
sess = Session(Terminal(cols=80, rows=24))
|
|
72
|
+
sess.source = PtySource(["bash"])
|
|
73
|
+
sess.claim_control("local", "human")
|
|
74
|
+
curses_ui.run(sess, None, title="bash")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Full API — classes, signatures, the observe/control contract, and worked examples — is in
|
|
78
|
+
[docs/REFERENCE.md](docs/REFERENCE.md).
|
|
79
|
+
|
|
80
|
+
The pieces:
|
|
81
|
+
|
|
82
|
+
- **`Terminal` / `PyteTerminal`** — the screen model. `Terminal` is a fixed-size character
|
|
83
|
+
grid (VT52 spirit: wrap/scroll, the common control chars, a handful of VT52 escapes), with
|
|
84
|
+
scrollback and no deps. `PyteTerminal` is a drop-in full-ANSI/VT100+ backend (wraps `pyte`,
|
|
85
|
+
the `ansi` extra) for programs that speak modern ANSI; same read interface (plus a `cells()`
|
|
86
|
+
view of per-cell SGR color), so the GUI renderers show color while the rest is unchanged.
|
|
87
|
+
- **`Source` / `PtySource` / `EngineSource` / `CastSource` / `TtyrecSource` / `AnsSource` /
|
|
88
|
+
`ThreeASource` / `PipeSource` / `ConPtySource`** — byte producers. `PtySource` runs an external
|
|
89
|
+
command on a real pty (POSIX); `EngineSource` wraps any in-process `runner(emit, readline)`
|
|
90
|
+
callable; `CastSource` / `TtyrecSource` replay a recorded `.cast` / `.ttyrec` session, and
|
|
91
|
+
`AnsSource` / `ThreeASource` play `.ans` / `.3a` art, through the same pipeline (original
|
|
92
|
+
timing, `speed`/`loop`; `replay_source(path)` picks by extension); `PipeSource` hosts a command
|
|
93
|
+
over plain pipes (no pty, any OS); `ConPtySource` hosts one on a Windows pseudo-console (ConPTY,
|
|
94
|
+
the `win` extra).
|
|
95
|
+
- **`Session`** — hosts a Source, drives the Terminal, and exposes **observe taps**
|
|
96
|
+
(`on_stream`, `on_frame`, `on_event`) and **control** (`send_input`, `feed_key`) plus a
|
|
97
|
+
talking-stick arbitration so exactly one controller types at a time.
|
|
98
|
+
- **`Recorder` / `render_video`** — `Recorder` writes the session's output stream to a `.cast`
|
|
99
|
+
or `.ttyrec` recording as it runs (the inverse of the replay sources; `tapterm --record`);
|
|
100
|
+
`render_video` encodes a recording to a real video file (mp4/webm/gif) via ffmpeg, with
|
|
101
|
+
size/zoom/font/speed and an area-of-interest crop (`tapterm --play X --render out.mp4`).
|
|
102
|
+
- **`BusServer` / `BusClient`** — the same observe/control contract over a Unix-domain
|
|
103
|
+
socket *or* TCP (a `(host, port)` tuple — works on Windows too), so an out-of-process
|
|
104
|
+
client (a logger, an automated driver, a remote renderer) can attach to a session. It's a
|
|
105
|
+
terminal control plane — **trusted-local**: the Unix socket is owner-only, TCP is
|
|
106
|
+
loopback-only unless `allow_remote=True`, and a `token=` adds an optional shared-secret
|
|
107
|
+
gate. Not a substitute for a tunnel on an untrusted network (no TLS).
|
|
108
|
+
- **`curses_ui` / `pygame_ui` / `arcade_ui` / `web_ui`** — renderers; each exposes
|
|
109
|
+
`run(session, runner, title=…)`. `pygame_ui` and `arcade_ui` are two backends for the same
|
|
110
|
+
green-phosphor window (SDL, or arcade/OpenGL); `web_ui` serves it in a browser over a
|
|
111
|
+
WebSocket (the `web` extra).
|
|
112
|
+
- **`compositor`** — tile several panels (`SessionBacking` for in-process, `BusBacking`
|
|
113
|
+
for remote) in one GUI window, with per-tile pan/zoom and focus.
|
|
114
|
+
|
|
115
|
+
## Demos & examples
|
|
116
|
+
|
|
117
|
+
`demos/` holds runnable showpieces — the SGR color chart, the green digital rain, the compositor
|
|
118
|
+
"mission control", `drive_vim` (a program driving a live `vim` over the control tap), and
|
|
119
|
+
`web_demo` (the browser renderer). `examples/` holds short, commented API examples — the observe
|
|
120
|
+
taps, writing a custom `Source`, driving a session over the bus, and `watch_and_drive` (a bot that
|
|
121
|
+
reads the output stream and types its decisions back). The [gallery](docs/GALLERY.md) has
|
|
122
|
+
screenshots and a clip of each.
|
|
123
|
+
|
|
124
|
+
## Platform
|
|
125
|
+
|
|
126
|
+
The core (Terminal/Session/taps/talking-stick), `EngineSource`, `CastSource`, `PipeSource`,
|
|
127
|
+
the renderers, and the TCP bus are cross-platform. `PtySource` uses `pty`/`termios`
|
|
128
|
+
(POSIX-only); on Windows, host via `ConPtySource` (the `win` extra, ConPTY — paired with
|
|
129
|
+
`--ansi`) or `PipeSource` (`--no-pty`), and use the TCP bus rather than a Unix socket. The
|
|
130
|
+
Windows ConPTY path is implemented but not yet exercised on real Windows — finishing it is
|
|
131
|
+
open work (see [docs/DESIGN.md](docs/DESIGN.md) §11). The GUI needs a display; the CUI needs a
|
|
132
|
+
terminal (and, on Windows, the `win` extra's `windows-curses`); `--headless` needs neither.
|
|
133
|
+
|
|
134
|
+
## Documentation
|
|
135
|
+
|
|
136
|
+
The rendered docs site is at **[nyxcraft.github.io/tappty](https://nyxcraft.github.io/tappty/)**
|
|
137
|
+
(built from `docs/` by `gh-pages/build_site.py` and published via GitHub Actions). The sources:
|
|
138
|
+
|
|
139
|
+
- **[docs/TAPTERM.md](docs/TAPTERM.md)** — the `tapterm` command in depth: every flag,
|
|
140
|
+
the CUI / GUI / headless modes, the terminal model, recordings, snapshots, recipes, and
|
|
141
|
+
troubleshooting. For *using* tapterm.
|
|
142
|
+
- **[docs/REFERENCE.md](docs/REFERENCE.md)** — the programming/API reference: every public
|
|
143
|
+
class and method with signatures, the observe/control contracts (snapshot dict, events,
|
|
144
|
+
roles), and worked examples. For *building on* the library.
|
|
145
|
+
- **[docs/DESIGN.md](docs/DESIGN.md)** — the architecture: the Source → Terminal → Session →
|
|
146
|
+
renderer/bus pipeline, the concurrency and security/trust models, and the design rationale.
|
|
147
|
+
For *modifying* tappty.
|
|
148
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — dated history, newest first: when tappty started, when
|
|
149
|
+
it first worked, and what's changed since. The remaining open work (publish to PyPI; verify
|
|
150
|
+
Windows) is in [docs/DESIGN.md](docs/DESIGN.md) §11.
|
|
151
|
+
|
|
152
|
+
## Tests & tooling
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
pip install -e '.[dev]' # quick: core suite (pyte + GUI tests skip)
|
|
156
|
+
pytest
|
|
157
|
+
|
|
158
|
+
pip install -e '.[dev,ansi,sdl]' # full: also the ANSI backend + headless GUI smoke
|
|
159
|
+
pytest
|
|
160
|
+
|
|
161
|
+
ruff check src tests # lint (E,F,W,I,B,UP); must be clean
|
|
162
|
+
ruff format src tests # format (line-length 99, black-style)
|
|
163
|
+
|
|
164
|
+
# no install needed, straight from a checkout:
|
|
165
|
+
PYTHONPATH=src python3 -m tappty.cli --headless -- echo hello
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Without the `ansi`/`sdl` extras, `pytest` skips the pyte and GUI tests (so it reports
|
|
169
|
+
fewer passes plus a couple of skips); install `.[dev,ansi,sdl]` to run the whole suite. CI
|
|
170
|
+
runs ruff + the full matrix on Python 3.9–3.13.
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT © Nicholas J. Kisseberth. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# tappty demos
|
|
2
|
+
|
|
3
|
+
Runnable showpieces — single-file apps you run to *see* a feature in action. Most are
|
|
4
|
+
**in-process** (an `EngineSource`), so they need no external program; `drive_vim` hosts a real
|
|
5
|
+
`vim` to show off observe-and-control. (For coding-level examples of how to build on the API,
|
|
6
|
+
see [`../examples/`](../examples/).)
|
|
7
|
+
|
|
8
|
+
| Demo | What it shows | Run |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| [color_chart.py](color_chart.py) | SGR color + attributes — bold/italic/underline/strike/blink/reverse and a 256-color strip | `python demos/color_chart.py` |
|
|
11
|
+
| [matrix_rain.py](matrix_rain.py) | green-phosphor "digital rain" on the dependency-free VT52 backend | `python demos/matrix_rain.py` |
|
|
12
|
+
| [mission_control.py](mission_control.py) | the compositor — four live sessions tiled in one window | `python demos/mission_control.py` |
|
|
13
|
+
| [drive_vim.py](drive_vim.py) | a program driving a real terminal app — an autopilot types into live `vim` over the control tap | `python demos/drive_vim.py` |
|
|
14
|
+
| [web_demo.py](web_demo.py) | the **web renderer** — the live terminal served as an HTML canvas, painted in a browser tab | `python demos/web_demo.py` |
|
|
15
|
+
|
|
16
|
+
Install the matching extra first:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pip install 'tappty[sdl,ansi]' # color_chart / mission_control / drive_vim (pygame + pyte)
|
|
20
|
+
pip install 'tappty[sdl]' # matrix_rain (pygame only; no color backend needed)
|
|
21
|
+
pip install 'tappty[web,ansi]' # web_demo (websockets + pyte); --shot also needs playwright
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`drive_vim` also needs `vim` (or `vi`) on your PATH; `web_demo --shot` needs Playwright +
|
|
25
|
+
Chromium (`pip install playwright && playwright install chromium`). To watch a closed loop that
|
|
26
|
+
**reads the screen and decides what to type**, see
|
|
27
|
+
[`../examples/watch_and_drive.py`](../examples/watch_and_drive.py).
|
|
28
|
+
|
|
29
|
+
Each demo also takes `--snapshot PATH`: instead of opening a window it renders
|
|
30
|
+
headless (SDL dummy driver) and writes a PNG. That's exactly how the
|
|
31
|
+
[documentation gallery](../docs/GALLERY.md) images are produced —
|
|
32
|
+
`gh-pages/screenshots.py` runs these same files with `--snapshot`.
|
|
33
|
+
|
|
34
|
+
`recordings/` holds short `.cast` sessions — real ANSI programs (`nyancat`, `cbonsai`) and the
|
|
35
|
+
autopilot-driven `vim` (`drive_vim.cast`) — recorded with `tapterm --record` and replayable with
|
|
36
|
+
**zero dependencies**:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
tapterm --play demos/recordings/nyancat.cast
|
|
40
|
+
tapterm --play demos/recordings/drive_vim.cast
|
|
41
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""tappty demo — an ANSI color & SGR-attribute chart.
|
|
3
|
+
|
|
4
|
+
Hosts a tiny in-process program (an EngineSource) that prints a color and
|
|
5
|
+
attribute chart, so you can watch tappty render SGR. No external program needed.
|
|
6
|
+
|
|
7
|
+
pip install 'tappty[sdl,ansi]'
|
|
8
|
+
python demos/color_chart.py # open the green-phosphor window
|
|
9
|
+
python demos/color_chart.py --snapshot c.png # render headless, write c.png instead
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
CSI = "\x1b["
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def runner(emit, readline):
|
|
20
|
+
"""An EngineSource program: print the chart, then wait for a keypress."""
|
|
21
|
+
emit(CSI + "2J" + CSI + "H")
|
|
22
|
+
emit(" tappty — SGR color & attributes\r\n\r\n")
|
|
23
|
+
|
|
24
|
+
emit(" foreground ")
|
|
25
|
+
for c in range(8):
|
|
26
|
+
emit(f"{CSI}3{c}m ██ {CSI}0m")
|
|
27
|
+
emit("\r\n (bold) ")
|
|
28
|
+
for c in range(8):
|
|
29
|
+
emit(f"{CSI}1;3{c}m ██ {CSI}0m")
|
|
30
|
+
emit("\r\n\r\n")
|
|
31
|
+
|
|
32
|
+
emit(" background ")
|
|
33
|
+
for c in range(8):
|
|
34
|
+
emit(f"{CSI}4{c}m {CSI}0m")
|
|
35
|
+
emit("\r\n\r\n")
|
|
36
|
+
|
|
37
|
+
emit(" attributes ")
|
|
38
|
+
emit(f"{CSI}1mbold{CSI}0m {CSI}3mitalic{CSI}0m {CSI}4munderline{CSI}0m ")
|
|
39
|
+
emit(f"{CSI}9mstrike{CSI}0m {CSI}5mblink{CSI}0m {CSI}7m reverse {CSI}0m\r\n\r\n")
|
|
40
|
+
|
|
41
|
+
emit(" 256-color ")
|
|
42
|
+
for i in range(16, 16 + 42): # a slice of the 6x6x6 cube
|
|
43
|
+
emit(f"{CSI}48;5;{i}m {CSI}0m")
|
|
44
|
+
emit("\r\n\r\n")
|
|
45
|
+
|
|
46
|
+
emit(" Uncolored text stays phosphor green — color appears only\r\n")
|
|
47
|
+
emit(" where the program asks for it.\r\n\r\n press a key to exit › ")
|
|
48
|
+
readline()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
ap = argparse.ArgumentParser(description="tappty color/SGR chart demo")
|
|
53
|
+
ap.add_argument("--snapshot", metavar="PNG", help="render headless and write a PNG")
|
|
54
|
+
ap.add_argument("--seconds", type=float, default=2.0, help="snapshot render-time cap")
|
|
55
|
+
args = ap.parse_args()
|
|
56
|
+
|
|
57
|
+
if args.snapshot: # must precede the pygame import
|
|
58
|
+
import os
|
|
59
|
+
|
|
60
|
+
os.environ.setdefault("SDL_VIDEODRIVER", "dummy")
|
|
61
|
+
os.environ.setdefault("SDL_AUDIODRIVER", "dummy")
|
|
62
|
+
os.environ.setdefault("PYGAME_HIDE_SUPPORT_PROMPT", "1")
|
|
63
|
+
|
|
64
|
+
from tappty import Session, pygame_ui
|
|
65
|
+
from tappty.pyte_terminal import PyteTerminal
|
|
66
|
+
from tappty.source import EngineSource
|
|
67
|
+
|
|
68
|
+
session = Session(PyteTerminal(64, 22), source=EngineSource(runner))
|
|
69
|
+
if args.snapshot:
|
|
70
|
+
base = args.snapshot[:-4] if args.snapshot.endswith(".png") else args.snapshot
|
|
71
|
+
pygame_ui.run(
|
|
72
|
+
session, None, title="tappty color chart", snapshot_path=base, max_seconds=args.seconds
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
pygame_ui.run(session, None, title="tappty color chart")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
main()
|