py-screen-mux 20261101.6__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.
- py_screen_mux-20261101.6/LICENSE +21 -0
- py_screen_mux-20261101.6/MANIFEST.in +3 -0
- py_screen_mux-20261101.6/PKG-INFO +203 -0
- py_screen_mux-20261101.6/README.md +180 -0
- py_screen_mux-20261101.6/deps/README.md +54 -0
- py_screen_mux-20261101.6/deps/libvterm/.bzrignore +13 -0
- py_screen_mux-20261101.6/deps/libvterm/CODE-MAP +81 -0
- py_screen_mux-20261101.6/deps/libvterm/CONTRIBUTING +22 -0
- py_screen_mux-20261101.6/deps/libvterm/LICENSE +23 -0
- py_screen_mux-20261101.6/deps/libvterm/Makefile +142 -0
- py_screen_mux-20261101.6/deps/libvterm/bin/unterm.c +280 -0
- py_screen_mux-20261101.6/deps/libvterm/bin/vterm-ctrl.c +354 -0
- py_screen_mux-20261101.6/deps/libvterm/bin/vterm-dump.c +245 -0
- py_screen_mux-20261101.6/deps/libvterm/doc/URLs +14 -0
- py_screen_mux-20261101.6/deps/libvterm/doc/seqs.txt +283 -0
- py_screen_mux-20261101.6/deps/libvterm/find-wide-chars.pl +30 -0
- py_screen_mux-20261101.6/deps/libvterm/include/vterm.h +637 -0
- py_screen_mux-20261101.6/deps/libvterm/include/vterm_keycodes.h +61 -0
- py_screen_mux-20261101.6/deps/libvterm/src/encoding/DECdrawing.inc +36 -0
- py_screen_mux-20261101.6/deps/libvterm/src/encoding/DECdrawing.tbl +31 -0
- py_screen_mux-20261101.6/deps/libvterm/src/encoding/uk.inc +6 -0
- py_screen_mux-20261101.6/deps/libvterm/src/encoding/uk.tbl +1 -0
- py_screen_mux-20261101.6/deps/libvterm/src/encoding.c +230 -0
- py_screen_mux-20261101.6/deps/libvterm/src/fullwidth.inc +111 -0
- py_screen_mux-20261101.6/deps/libvterm/src/keyboard.c +226 -0
- py_screen_mux-20261101.6/deps/libvterm/src/mouse.c +99 -0
- py_screen_mux-20261101.6/deps/libvterm/src/parser.c +402 -0
- py_screen_mux-20261101.6/deps/libvterm/src/pen.c +607 -0
- py_screen_mux-20261101.6/deps/libvterm/src/rect.h +56 -0
- py_screen_mux-20261101.6/deps/libvterm/src/screen.c +1191 -0
- py_screen_mux-20261101.6/deps/libvterm/src/state.c +2370 -0
- py_screen_mux-20261101.6/deps/libvterm/src/unicode.c +313 -0
- py_screen_mux-20261101.6/deps/libvterm/src/utf8.h +39 -0
- py_screen_mux-20261101.6/deps/libvterm/src/vterm.c +430 -0
- py_screen_mux-20261101.6/deps/libvterm/src/vterm_internal.h +297 -0
- py_screen_mux-20261101.6/deps/libvterm/t/02parser.test +266 -0
- py_screen_mux-20261101.6/deps/libvterm/t/03encoding_utf8.test +122 -0
- py_screen_mux-20261101.6/deps/libvterm/t/10state_putglyph.test +74 -0
- py_screen_mux-20261101.6/deps/libvterm/t/11state_movecursor.test +224 -0
- py_screen_mux-20261101.6/deps/libvterm/t/12state_scroll.test +156 -0
- py_screen_mux-20261101.6/deps/libvterm/t/13state_edit.test +304 -0
- py_screen_mux-20261101.6/deps/libvterm/t/14state_encoding.test +105 -0
- py_screen_mux-20261101.6/deps/libvterm/t/15state_mode.test +86 -0
- py_screen_mux-20261101.6/deps/libvterm/t/16state_resize.test +48 -0
- py_screen_mux-20261101.6/deps/libvterm/t/17state_mouse.test +191 -0
- py_screen_mux-20261101.6/deps/libvterm/t/18state_termprops.test +42 -0
- py_screen_mux-20261101.6/deps/libvterm/t/20state_wrapping.test +69 -0
- py_screen_mux-20261101.6/deps/libvterm/t/21state_tabstops.test +60 -0
- py_screen_mux-20261101.6/deps/libvterm/t/22state_save.test +64 -0
- py_screen_mux-20261101.6/deps/libvterm/t/25state_input.test +157 -0
- py_screen_mux-20261101.6/deps/libvterm/t/26state_query.test +63 -0
- py_screen_mux-20261101.6/deps/libvterm/t/27state_reset.test +32 -0
- py_screen_mux-20261101.6/deps/libvterm/t/28state_dbl_wh.test +61 -0
- py_screen_mux-20261101.6/deps/libvterm/t/29state_fallback.test +31 -0
- py_screen_mux-20261101.6/deps/libvterm/t/30state_pen.test +133 -0
- py_screen_mux-20261101.6/deps/libvterm/t/31state_rep.test +128 -0
- py_screen_mux-20261101.6/deps/libvterm/t/32state_flow.test +28 -0
- py_screen_mux-20261101.6/deps/libvterm/t/40state_selection.test +89 -0
- py_screen_mux-20261101.6/deps/libvterm/t/60screen_ascii.test +69 -0
- py_screen_mux-20261101.6/deps/libvterm/t/61screen_unicode.test +47 -0
- py_screen_mux-20261101.6/deps/libvterm/t/62screen_damage.test +155 -0
- py_screen_mux-20261101.6/deps/libvterm/t/63screen_resize.test +117 -0
- py_screen_mux-20261101.6/deps/libvterm/t/64screen_pen.test +71 -0
- py_screen_mux-20261101.6/deps/libvterm/t/65screen_protect.test +16 -0
- py_screen_mux-20261101.6/deps/libvterm/t/66screen_extent.test +11 -0
- py_screen_mux-20261101.6/deps/libvterm/t/67screen_dbl_wh.test +38 -0
- py_screen_mux-20261101.6/deps/libvterm/t/68screen_termprops.test +17 -0
- py_screen_mux-20261101.6/deps/libvterm/t/69screen_reflow.test +88 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_01-movement-1.test +87 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_01-movement-2.test +40 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_01-movement-3.test +21 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_01-movement-4.test +36 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_02-screen-1.test +18 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_02-screen-2.test +29 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_02-screen-3.test +16 -0
- py_screen_mux-20261101.6/deps/libvterm/t/90vttest_02-screen-4.test +17 -0
- py_screen_mux-20261101.6/deps/libvterm/t/92lp1640917.test +13 -0
- py_screen_mux-20261101.6/deps/libvterm/t/harness.c +1235 -0
- py_screen_mux-20261101.6/deps/libvterm/t/run-test.pl +234 -0
- py_screen_mux-20261101.6/deps/libvterm/tbl2inc_c.pl +30 -0
- py_screen_mux-20261101.6/deps/libvterm/vterm.pc.in +8 -0
- py_screen_mux-20261101.6/pyproject.toml +51 -0
- py_screen_mux-20261101.6/setup.cfg +4 -0
- py_screen_mux-20261101.6/setup.py +24 -0
- py_screen_mux-20261101.6/src/py_screen/__init__.py +109 -0
- py_screen_mux-20261101.6/src/py_screen/__main__.py +3 -0
- py_screen_mux-20261101.6/src/py_screen/_vterm.c +932 -0
- py_screen_mux-20261101.6/src/py_screen/brain.py +1153 -0
- py_screen_mux-20261101.6/src/py_screen/client.py +97 -0
- py_screen_mux-20261101.6/src/py_screen/config.py +143 -0
- py_screen_mux-20261101.6/src/py_screen/keeper.py +280 -0
- py_screen_mux-20261101.6/src/py_screen/layout.py +219 -0
- py_screen_mux-20261101.6/src/py_screen/mouse.py +92 -0
- py_screen_mux-20261101.6/src/py_screen/proto.py +75 -0
- py_screen_mux-20261101.6/src/py_screen/screen.py +292 -0
- py_screen_mux-20261101.6/src/py_screen/term.py +167 -0
- py_screen_mux-20261101.6/src/py_screen/vterm.py +16 -0
- py_screen_mux-20261101.6/src/py_screen_mux.egg-info/PKG-INFO +203 -0
- py_screen_mux-20261101.6/src/py_screen_mux.egg-info/SOURCES.txt +108 -0
- py_screen_mux-20261101.6/src/py_screen_mux.egg-info/dependency_links.txt +1 -0
- py_screen_mux-20261101.6/src/py_screen_mux.egg-info/entry_points.txt +2 -0
- py_screen_mux-20261101.6/src/py_screen_mux.egg-info/requires.txt +1 -0
- py_screen_mux-20261101.6/src/py_screen_mux.egg-info/top_level.txt +1 -0
- py_screen_mux-20261101.6/tests/test_flood.py +106 -0
- py_screen_mux-20261101.6/tests/test_m0.py +102 -0
- py_screen_mux-20261101.6/tests/test_m1.py +160 -0
- py_screen_mux-20261101.6/tests/test_mouse.py +144 -0
- py_screen_mux-20261101.6/tests/test_parser.py +120 -0
- py_screen_mux-20261101.6/tests/test_redraw.py +97 -0
- py_screen_mux-20261101.6/tests/test_vterm.py +125 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cong Zhang
|
|
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.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-screen-mux
|
|
3
|
+
Version: 20261101.6
|
|
4
|
+
Summary: py-screen: a terminal workspace (multiplexer) of my own, with the pys command
|
|
5
|
+
Author-email: Cong Zhang <13283869+congzhangzh@users.noreply.github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/congzhangzh/py-screen
|
|
8
|
+
Project-URL: Source, https://github.com/congzhangzh/py-screen
|
|
9
|
+
Project-URL: Releases, https://github.com/congzhangzh/py-screen/releases
|
|
10
|
+
Project-URL: Issues, https://github.com/congzhangzh/py-screen/issues
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: C
|
|
16
|
+
Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
License-File: deps/libvterm/LICENSE
|
|
21
|
+
Requires-Dist: pyte>=0.8.2
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# py-screen
|
|
25
|
+
|
|
26
|
+
A terminal workspace of my own, in Python: not a clone of screen or tmux, built for fun,
|
|
27
|
+
made more powerful and improved by daily use. The design is in [`spec/`](https://github.com/congzhangzh/py-screen/tree/main/spec/); the
|
|
28
|
+
current version is [`spec/spec.v3.md`](https://github.com/congzhangzh/py-screen/blob/main/spec/spec.v3.md).
|
|
29
|
+
|
|
30
|
+
Status: **M1 in progress** (Linux; Windows pieces checked on CI). One server, many windows, attach/detach, hot reload, flood-safe typing.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
client (pys attach) ──► brain (Python, restartable) ──► keeper (tiny, stable) ──► ptys
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **keeper** owns every pty and child process and is the only reader of their output
|
|
39
|
+
(a byte pump with a ring buffer per window). It restarts the brain when it exits.
|
|
40
|
+
- **brain** holds a pyte screen per window, renders the current one plus a status line,
|
|
41
|
+
handles keys and answers RPCs. `pys reload` restarts it; shells never notice, because
|
|
42
|
+
the ptys live in the keeper. The new brain replays the ring buffers, then nudges each pty
|
|
43
|
+
(SIGWINCH) so full-screen apps repaint.
|
|
44
|
+
- **client** puts the terminal in raw mode, forwards keys and size, prints frames, and
|
|
45
|
+
reconnects by itself across a brain reload.
|
|
46
|
+
|
|
47
|
+
### Floods: the screen model is lossless
|
|
48
|
+
|
|
49
|
+
Every byte a window writes is parsed, in order, whether the window is shown or hidden,
|
|
50
|
+
attached or not. That matters most for apps that draw by diff, like Claude Code: they
|
|
51
|
+
write only the cells they think changed and never rewrite a cell they think is right, so
|
|
52
|
+
a single lost byte leaves wrong cells for good (and a lost mode switch, such as the
|
|
53
|
+
alternate screen or bracketed paste, breaks input too). Until 2026-11-01 py-screen parsed
|
|
54
|
+
only the tail of a flood and dropped a hidden window's output past 128 KB; that is what
|
|
55
|
+
left stray characters in Claude's status line.
|
|
56
|
+
|
|
57
|
+
Parsing is the slow part (pyte in Python; py-screen's fast paths for plain text, line
|
|
58
|
+
feeds and CSI sequences take it from 0.85 to 1.7 MB/s of scrolling text), so:
|
|
59
|
+
|
|
60
|
+
- **Backpressure, not dropping.** The keeper gives each window a **credit** of 256 KB
|
|
61
|
+
un-acked output and stops reading that pty past it. The brain acks bytes only once it
|
|
62
|
+
has parsed them, so a flooding app simply waits on its write, as under GNU screen. One
|
|
63
|
+
window can't fill the shared stream, so another window's echo never waits behind it.
|
|
64
|
+
- **Small steps.** The brain parses in steps of 2 ms (2 KB per window per turn, shown
|
|
65
|
+
windows first, the others round robin), then goes back to keys and frames.
|
|
66
|
+
- **Keys first.** For 100 ms after a key, only shown windows are parsed: nothing else
|
|
67
|
+
stands between a key and its echo (about 2 ms median while a hidden window floods).
|
|
68
|
+
|
|
69
|
+
A flood runs at the parser's speed. With the vterm engine (libvterm, in C) that is faster
|
|
70
|
+
than GNU screen: `seq 1 5000000` takes 1.6 s against screen's 13.4 s, a 50 MB coloured log
|
|
71
|
+
0.9 s against 8.1 s ([`bench/README.md`](https://github.com/congzhangzh/py-screen/blob/main/bench/README.md) has the history).
|
|
72
|
+
|
|
73
|
+
**Screen engines.** Each window's screen is a `term.Term`; `PYS_TERM` picks the engine:
|
|
74
|
+
|
|
75
|
+
| Engine | What | When |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `vterm` (default) | [libvterm](https://github.com/congzhangzh/py-screen/blob/main/deps/README.md)'s parser and state, compiled in, with py-screen's own screen store | When the C extension was built (needs a C compiler at install; release wheels have it) |
|
|
78
|
+
| `pyte` | [pyte](https://github.com/selectel/pyte) with pure-Python fast paths | Always there; about 20× slower on floods |
|
|
79
|
+
|
|
80
|
+
`PYS_TERM=vterm` makes a missing extension an error instead of a quiet fallback.
|
|
81
|
+
|
|
82
|
+
A sequence that pyte can't apply (an extra parameter, a `?` where none is defined, an
|
|
83
|
+
unknown erase value) is ignored, as terminals do; the bytes after it are still parsed.
|
|
84
|
+
|
|
85
|
+
## Releases
|
|
86
|
+
|
|
87
|
+
Rolling releases: every push to `main` that passes is published as `<YYYYMMDD>.<N>`, the
|
|
88
|
+
version and the tag alike, on [PyPI as `py-screen-mux`](https://pypi.org/project/py-screen-mux/)
|
|
89
|
+
and as a [GitHub release](https://github.com/congzhangzh/py-screen/releases), once, never
|
|
90
|
+
replaced. Pushes that change only docs, specs or bench results publish nothing. A release must pass, in [`release.yml`](https://github.com/congzhangzh/py-screen/blob/main/.github/workflows/release.yml):
|
|
91
|
+
|
|
92
|
+
- the suite on Linux and macOS, on both screen engines (vterm and pyte);
|
|
93
|
+
- the vterm engine built with ASan + UBSan and fuzzed against libvterm's own screen
|
|
94
|
+
(a long run every night, in [`sanitize.yml`](https://github.com/congzhangzh/py-screen/blob/main/.github/workflows/sanitize.yml));
|
|
95
|
+
- a parse-speed check: vterm's lead over pyte must keep at least half of the newest
|
|
96
|
+
recorded run's (`release/bench_gate.py`, against `bench/results/parse-*.json`);
|
|
97
|
+
- each wheel, built by cibuildwheel from the sdist, installed and smoke-tested on the vterm
|
|
98
|
+
engine on its own system: Linux x86_64 and aarch64 (manylinux), macOS arm64 and x86_64.
|
|
99
|
+
|
|
100
|
+
Install with [uv](https://docs.astral.sh/uv/), which fetches Python 3.14 if needed and picks
|
|
101
|
+
the wheel for your system:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uv tool install py-screen-mux # the command is pys
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The package is `py-screen-mux` on PyPI because `py-screen` is too close to an existing
|
|
108
|
+
`pyscreen` there; the import is `py_screen` and the command `pys`.
|
|
109
|
+
|
|
110
|
+
Elsewhere, the sdist builds with a C compiler, or runs the pure-Python pyte engine without one.
|
|
111
|
+
Windows joins once its platform layer lands.
|
|
112
|
+
|
|
113
|
+
## Use
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv tool install -e . # puts `pys` in ~/.local/bin, running this checkout (edits apply on reload)
|
|
117
|
+
pys # attach (starts the server on first use)
|
|
118
|
+
pys ls [--json] # windows
|
|
119
|
+
pys new [--select] [-- cmd …]
|
|
120
|
+
pys send 0 'make test' --enter
|
|
121
|
+
pys capture 0 # the window's screen as text
|
|
122
|
+
pys reload # new brain, same shells
|
|
123
|
+
pys kill # end everything
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Keys: the prefix is **`C-a`**, or **`C-]`** when you run `pys` inside GNU screen (`$STY`
|
|
127
|
+
set), so the two don't fight. Override with `PREFIX` in the config or `PYS_PREFIX`.
|
|
128
|
+
|
|
129
|
+
| Keys | Action |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `c` / `C-c` | new window in the focused window's directory |
|
|
132
|
+
| `\|` / `-` | split side by side / stacked, with a new window in the same dir |
|
|
133
|
+
| `\` / `_` | empty split side by side / stacked |
|
|
134
|
+
| `h` `j` `k` `l` | move the focus between panes |
|
|
135
|
+
| `r` then `h/l/j/k`, `=` | resize mode (any other key ends it) · `=` equalise panes |
|
|
136
|
+
| `x` / `X` | close the pane (window keeps running) / kill the window |
|
|
137
|
+
| `n` `p` `0`–`9`, `Alt-0`–`Alt-9` | next, previous, select |
|
|
138
|
+
| `Space` or the prefix twice | toggle to the last window |
|
|
139
|
+
| `w` `"` `F2` | window list (`j/k`, arrows, digits, Enter, Esc) |
|
|
140
|
+
| `,` | rename the window |
|
|
141
|
+
| `?` | help: the windows (a digit or a click selects one), these keys, your config's bindings; Space pages when it does not fit |
|
|
142
|
+
| `a` | send a literal prefix |
|
|
143
|
+
| `d` · `C-l` · `R` · `Q` | detach · redraw · reload the brain · quit everything |
|
|
144
|
+
|
|
145
|
+
Without the prefix: `Alt-0`…`Alt-9` select, `F2` opens the list, **`Alt-Tab` /
|
|
146
|
+
`Alt-Shift-Tab`** go to the next / previous window. Desktops usually keep Alt-Tab for
|
|
147
|
+
themselves (GNOME: `org.gnome.desktop.wm.keybindings switch-applications`), so it reaches
|
|
148
|
+
py-screen only where the desktop lets it through, e.g. over ssh from another machine.
|
|
149
|
+
Inside GNU screen, screen's own `bindkey` lines may also take the Alt keys first.
|
|
150
|
+
|
|
151
|
+
### Mouse
|
|
152
|
+
|
|
153
|
+
On by default, like tmux's `mouse on`:
|
|
154
|
+
|
|
155
|
+
| Mouse | Does |
|
|
156
|
+
|-------|------|
|
|
157
|
+
| click a pane | focus it; its app gets the click too if it asked for the mouse |
|
|
158
|
+
| click a window in the status line | select it |
|
|
159
|
+
| drag a `│` separator or a caption line | resize the panes on both sides |
|
|
160
|
+
| wheel | goes to the pane under the pointer, focused or not; a full-screen app that didn't ask for the mouse (less, man) gets arrow keys |
|
|
161
|
+
| Shift+drag | select text in your terminal (most terminals; the mouse is pys's otherwise) |
|
|
162
|
+
|
|
163
|
+
Every report is routed to the pane under the pointer and rewritten in that pane's
|
|
164
|
+
coordinates, in the encoding the app asked for (SGR 1006, or the old X10 form). A press
|
|
165
|
+
grabs its pane: the drag and the release go there even if the pointer leaves it.
|
|
166
|
+
`MOUSE = False` in the config gives plain drags back to the terminal; the mouse is then
|
|
167
|
+
pys's only while the focused app asks for it (still translated per pane).
|
|
168
|
+
|
|
169
|
+
### Config
|
|
170
|
+
|
|
171
|
+
`~/.config/pys/config.py` (plain Python, read on every brain start, so `C-a R` applies an
|
|
172
|
+
edit): status widgets and extra key bindings. See `src/py_screen/config.py` for the helpers.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
WIDGETS = [cmd(["git", "-C", "{cwd}", "branch", "--show-current"], every=10), load(), clock()]
|
|
176
|
+
BIND = {"T": ("top", ["htop"])} # C-a T opens a window running htop
|
|
177
|
+
MOUSE = True # False: plain drags select text in the terminal
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The layout (splits, focus, renamed titles) is saved on every change, so a reload keeps it.
|
|
181
|
+
|
|
182
|
+
Sockets live in `$XDG_RUNTIME_DIR/pys/` (`PYS_DIR` overrides; keep it short: an AF_UNIX
|
|
183
|
+
path is limited to 108 bytes). Server log: `server.log` in that dir.
|
|
184
|
+
|
|
185
|
+
## Test and measure
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
uv run pytest -s # M0 done-criteria (spec §6)
|
|
189
|
+
uv run python bench/m0_speed.py --work <scratch dir> # speed vs GNU screen (spec §8.1)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Results of each run are kept in [`bench/results/`](https://github.com/congzhangzh/py-screen/tree/main/bench/results/), one file per run, and [`bench/README.md`](https://github.com/congzhangzh/py-screen/blob/main/bench/README.md) tells how the speed evolved, one row per run.
|
|
193
|
+
|
|
194
|
+
## Known gaps (M1)
|
|
195
|
+
|
|
196
|
+
- A mouse report split across two reads is passed on as keys (rare; the outer terminal
|
|
197
|
+
writes each report at once).
|
|
198
|
+
- After a brain restart the screens are rebuilt from the keeper's last 2 MB per window; if
|
|
199
|
+
that ring wrapped, older state is gone and full-screen apps get a `SIGWINCH` to repaint.
|
|
200
|
+
Keeping the screens across a planned reload is open.
|
|
201
|
+
- Floods are bounded by pyte's speed (see "Floods").
|
|
202
|
+
- No scrollback view yet (M2).
|
|
203
|
+
- Linux (and macOS) only for now; the Windows pieces are checked by the manual CI run.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# py-screen
|
|
2
|
+
|
|
3
|
+
A terminal workspace of my own, in Python: not a clone of screen or tmux, built for fun,
|
|
4
|
+
made more powerful and improved by daily use. The design is in [`spec/`](https://github.com/congzhangzh/py-screen/tree/main/spec/); the
|
|
5
|
+
current version is [`spec/spec.v3.md`](https://github.com/congzhangzh/py-screen/blob/main/spec/spec.v3.md).
|
|
6
|
+
|
|
7
|
+
Status: **M1 in progress** (Linux; Windows pieces checked on CI). One server, many windows, attach/detach, hot reload, flood-safe typing.
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
client (pys attach) ──► brain (Python, restartable) ──► keeper (tiny, stable) ──► ptys
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **keeper** owns every pty and child process and is the only reader of their output
|
|
16
|
+
(a byte pump with a ring buffer per window). It restarts the brain when it exits.
|
|
17
|
+
- **brain** holds a pyte screen per window, renders the current one plus a status line,
|
|
18
|
+
handles keys and answers RPCs. `pys reload` restarts it; shells never notice, because
|
|
19
|
+
the ptys live in the keeper. The new brain replays the ring buffers, then nudges each pty
|
|
20
|
+
(SIGWINCH) so full-screen apps repaint.
|
|
21
|
+
- **client** puts the terminal in raw mode, forwards keys and size, prints frames, and
|
|
22
|
+
reconnects by itself across a brain reload.
|
|
23
|
+
|
|
24
|
+
### Floods: the screen model is lossless
|
|
25
|
+
|
|
26
|
+
Every byte a window writes is parsed, in order, whether the window is shown or hidden,
|
|
27
|
+
attached or not. That matters most for apps that draw by diff, like Claude Code: they
|
|
28
|
+
write only the cells they think changed and never rewrite a cell they think is right, so
|
|
29
|
+
a single lost byte leaves wrong cells for good (and a lost mode switch, such as the
|
|
30
|
+
alternate screen or bracketed paste, breaks input too). Until 2026-11-01 py-screen parsed
|
|
31
|
+
only the tail of a flood and dropped a hidden window's output past 128 KB; that is what
|
|
32
|
+
left stray characters in Claude's status line.
|
|
33
|
+
|
|
34
|
+
Parsing is the slow part (pyte in Python; py-screen's fast paths for plain text, line
|
|
35
|
+
feeds and CSI sequences take it from 0.85 to 1.7 MB/s of scrolling text), so:
|
|
36
|
+
|
|
37
|
+
- **Backpressure, not dropping.** The keeper gives each window a **credit** of 256 KB
|
|
38
|
+
un-acked output and stops reading that pty past it. The brain acks bytes only once it
|
|
39
|
+
has parsed them, so a flooding app simply waits on its write, as under GNU screen. One
|
|
40
|
+
window can't fill the shared stream, so another window's echo never waits behind it.
|
|
41
|
+
- **Small steps.** The brain parses in steps of 2 ms (2 KB per window per turn, shown
|
|
42
|
+
windows first, the others round robin), then goes back to keys and frames.
|
|
43
|
+
- **Keys first.** For 100 ms after a key, only shown windows are parsed: nothing else
|
|
44
|
+
stands between a key and its echo (about 2 ms median while a hidden window floods).
|
|
45
|
+
|
|
46
|
+
A flood runs at the parser's speed. With the vterm engine (libvterm, in C) that is faster
|
|
47
|
+
than GNU screen: `seq 1 5000000` takes 1.6 s against screen's 13.4 s, a 50 MB coloured log
|
|
48
|
+
0.9 s against 8.1 s ([`bench/README.md`](https://github.com/congzhangzh/py-screen/blob/main/bench/README.md) has the history).
|
|
49
|
+
|
|
50
|
+
**Screen engines.** Each window's screen is a `term.Term`; `PYS_TERM` picks the engine:
|
|
51
|
+
|
|
52
|
+
| Engine | What | When |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `vterm` (default) | [libvterm](https://github.com/congzhangzh/py-screen/blob/main/deps/README.md)'s parser and state, compiled in, with py-screen's own screen store | When the C extension was built (needs a C compiler at install; release wheels have it) |
|
|
55
|
+
| `pyte` | [pyte](https://github.com/selectel/pyte) with pure-Python fast paths | Always there; about 20× slower on floods |
|
|
56
|
+
|
|
57
|
+
`PYS_TERM=vterm` makes a missing extension an error instead of a quiet fallback.
|
|
58
|
+
|
|
59
|
+
A sequence that pyte can't apply (an extra parameter, a `?` where none is defined, an
|
|
60
|
+
unknown erase value) is ignored, as terminals do; the bytes after it are still parsed.
|
|
61
|
+
|
|
62
|
+
## Releases
|
|
63
|
+
|
|
64
|
+
Rolling releases: every push to `main` that passes is published as `<YYYYMMDD>.<N>`, the
|
|
65
|
+
version and the tag alike, on [PyPI as `py-screen-mux`](https://pypi.org/project/py-screen-mux/)
|
|
66
|
+
and as a [GitHub release](https://github.com/congzhangzh/py-screen/releases), once, never
|
|
67
|
+
replaced. Pushes that change only docs, specs or bench results publish nothing. A release must pass, in [`release.yml`](https://github.com/congzhangzh/py-screen/blob/main/.github/workflows/release.yml):
|
|
68
|
+
|
|
69
|
+
- the suite on Linux and macOS, on both screen engines (vterm and pyte);
|
|
70
|
+
- the vterm engine built with ASan + UBSan and fuzzed against libvterm's own screen
|
|
71
|
+
(a long run every night, in [`sanitize.yml`](https://github.com/congzhangzh/py-screen/blob/main/.github/workflows/sanitize.yml));
|
|
72
|
+
- a parse-speed check: vterm's lead over pyte must keep at least half of the newest
|
|
73
|
+
recorded run's (`release/bench_gate.py`, against `bench/results/parse-*.json`);
|
|
74
|
+
- each wheel, built by cibuildwheel from the sdist, installed and smoke-tested on the vterm
|
|
75
|
+
engine on its own system: Linux x86_64 and aarch64 (manylinux), macOS arm64 and x86_64.
|
|
76
|
+
|
|
77
|
+
Install with [uv](https://docs.astral.sh/uv/), which fetches Python 3.14 if needed and picks
|
|
78
|
+
the wheel for your system:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv tool install py-screen-mux # the command is pys
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The package is `py-screen-mux` on PyPI because `py-screen` is too close to an existing
|
|
85
|
+
`pyscreen` there; the import is `py_screen` and the command `pys`.
|
|
86
|
+
|
|
87
|
+
Elsewhere, the sdist builds with a C compiler, or runs the pure-Python pyte engine without one.
|
|
88
|
+
Windows joins once its platform layer lands.
|
|
89
|
+
|
|
90
|
+
## Use
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uv tool install -e . # puts `pys` in ~/.local/bin, running this checkout (edits apply on reload)
|
|
94
|
+
pys # attach (starts the server on first use)
|
|
95
|
+
pys ls [--json] # windows
|
|
96
|
+
pys new [--select] [-- cmd …]
|
|
97
|
+
pys send 0 'make test' --enter
|
|
98
|
+
pys capture 0 # the window's screen as text
|
|
99
|
+
pys reload # new brain, same shells
|
|
100
|
+
pys kill # end everything
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Keys: the prefix is **`C-a`**, or **`C-]`** when you run `pys` inside GNU screen (`$STY`
|
|
104
|
+
set), so the two don't fight. Override with `PREFIX` in the config or `PYS_PREFIX`.
|
|
105
|
+
|
|
106
|
+
| Keys | Action |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `c` / `C-c` | new window in the focused window's directory |
|
|
109
|
+
| `\|` / `-` | split side by side / stacked, with a new window in the same dir |
|
|
110
|
+
| `\` / `_` | empty split side by side / stacked |
|
|
111
|
+
| `h` `j` `k` `l` | move the focus between panes |
|
|
112
|
+
| `r` then `h/l/j/k`, `=` | resize mode (any other key ends it) · `=` equalise panes |
|
|
113
|
+
| `x` / `X` | close the pane (window keeps running) / kill the window |
|
|
114
|
+
| `n` `p` `0`–`9`, `Alt-0`–`Alt-9` | next, previous, select |
|
|
115
|
+
| `Space` or the prefix twice | toggle to the last window |
|
|
116
|
+
| `w` `"` `F2` | window list (`j/k`, arrows, digits, Enter, Esc) |
|
|
117
|
+
| `,` | rename the window |
|
|
118
|
+
| `?` | help: the windows (a digit or a click selects one), these keys, your config's bindings; Space pages when it does not fit |
|
|
119
|
+
| `a` | send a literal prefix |
|
|
120
|
+
| `d` · `C-l` · `R` · `Q` | detach · redraw · reload the brain · quit everything |
|
|
121
|
+
|
|
122
|
+
Without the prefix: `Alt-0`…`Alt-9` select, `F2` opens the list, **`Alt-Tab` /
|
|
123
|
+
`Alt-Shift-Tab`** go to the next / previous window. Desktops usually keep Alt-Tab for
|
|
124
|
+
themselves (GNOME: `org.gnome.desktop.wm.keybindings switch-applications`), so it reaches
|
|
125
|
+
py-screen only where the desktop lets it through, e.g. over ssh from another machine.
|
|
126
|
+
Inside GNU screen, screen's own `bindkey` lines may also take the Alt keys first.
|
|
127
|
+
|
|
128
|
+
### Mouse
|
|
129
|
+
|
|
130
|
+
On by default, like tmux's `mouse on`:
|
|
131
|
+
|
|
132
|
+
| Mouse | Does |
|
|
133
|
+
|-------|------|
|
|
134
|
+
| click a pane | focus it; its app gets the click too if it asked for the mouse |
|
|
135
|
+
| click a window in the status line | select it |
|
|
136
|
+
| drag a `│` separator or a caption line | resize the panes on both sides |
|
|
137
|
+
| wheel | goes to the pane under the pointer, focused or not; a full-screen app that didn't ask for the mouse (less, man) gets arrow keys |
|
|
138
|
+
| Shift+drag | select text in your terminal (most terminals; the mouse is pys's otherwise) |
|
|
139
|
+
|
|
140
|
+
Every report is routed to the pane under the pointer and rewritten in that pane's
|
|
141
|
+
coordinates, in the encoding the app asked for (SGR 1006, or the old X10 form). A press
|
|
142
|
+
grabs its pane: the drag and the release go there even if the pointer leaves it.
|
|
143
|
+
`MOUSE = False` in the config gives plain drags back to the terminal; the mouse is then
|
|
144
|
+
pys's only while the focused app asks for it (still translated per pane).
|
|
145
|
+
|
|
146
|
+
### Config
|
|
147
|
+
|
|
148
|
+
`~/.config/pys/config.py` (plain Python, read on every brain start, so `C-a R` applies an
|
|
149
|
+
edit): status widgets and extra key bindings. See `src/py_screen/config.py` for the helpers.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
WIDGETS = [cmd(["git", "-C", "{cwd}", "branch", "--show-current"], every=10), load(), clock()]
|
|
153
|
+
BIND = {"T": ("top", ["htop"])} # C-a T opens a window running htop
|
|
154
|
+
MOUSE = True # False: plain drags select text in the terminal
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The layout (splits, focus, renamed titles) is saved on every change, so a reload keeps it.
|
|
158
|
+
|
|
159
|
+
Sockets live in `$XDG_RUNTIME_DIR/pys/` (`PYS_DIR` overrides; keep it short: an AF_UNIX
|
|
160
|
+
path is limited to 108 bytes). Server log: `server.log` in that dir.
|
|
161
|
+
|
|
162
|
+
## Test and measure
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uv run pytest -s # M0 done-criteria (spec §6)
|
|
166
|
+
uv run python bench/m0_speed.py --work <scratch dir> # speed vs GNU screen (spec §8.1)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Results of each run are kept in [`bench/results/`](https://github.com/congzhangzh/py-screen/tree/main/bench/results/), one file per run, and [`bench/README.md`](https://github.com/congzhangzh/py-screen/blob/main/bench/README.md) tells how the speed evolved, one row per run.
|
|
170
|
+
|
|
171
|
+
## Known gaps (M1)
|
|
172
|
+
|
|
173
|
+
- A mouse report split across two reads is passed on as keys (rare; the outer terminal
|
|
174
|
+
writes each report at once).
|
|
175
|
+
- After a brain restart the screens are rebuilt from the keeper's last 2 MB per window; if
|
|
176
|
+
that ring wrapped, older state is gone and full-screen apps get a `SIGWINCH` to repaint.
|
|
177
|
+
Keeping the screens across a planned reload is open.
|
|
178
|
+
- Floods are bounded by pyte's speed (see "Floods").
|
|
179
|
+
- No scrollback view yet (M2).
|
|
180
|
+
- Linux (and macOS) only for now; the Windows pieces are checked by the manual CI run.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Third-party code
|
|
2
|
+
|
|
3
|
+
## libvterm 0.3.3 (`deps/libvterm`, MIT, see its `LICENSE`)
|
|
4
|
+
|
|
5
|
+
The terminal state machine behind the `vterm` screen engine. Upstream lives in
|
|
6
|
+
Bazaar on Launchpad; this tree comes from the git mirror Neovim kept of it.
|
|
7
|
+
|
|
8
|
+
| What | Value |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Added with | `git read-tree --prefix=deps/libvterm -u v0.3.3^{tree}` after fetching the tag: one plain commit, no merge, so history stays linear; its `git-subtree-dir:` / `git-subtree-split:` trailers are what `git subtree` reads |
|
|
11
|
+
| Upstream commit | `9d6d2112335080312ef8c36667fa717ded4f7daf` (tag `v0.3.3`) |
|
|
12
|
+
| Release tarball | `libvterm-0.3.3.tar.gz`, sha256 `09156f43dd2128bd347cbeebe50d9a571d32c64e0cf18d211197946aff7226e0`, the same file as Debian's `libvterm_0.3.3.orig.tar.gz` |
|
|
13
|
+
| Checked | `src/` and `include/` equal the tarball's, file for file |
|
|
14
|
+
| Tag vs tarball | The tag also has `doc/`, `CODE-MAP`, the `.tbl` sources and `tbl2inc_c.pl`; its `Makefile` and `vterm.pc.in` differ (the tarball strips the dist section); `t/69screen_sb_clear.test` is in the tarball only; the tarball has the generated `.inc` files (change 1) |
|
|
15
|
+
|
|
16
|
+
Changes on top of upstream, each its own commit (`git log -- deps/libvterm`):
|
|
17
|
+
|
|
18
|
+
1. `src/encoding/DECdrawing.inc` and `uk.inc`: generated by upstream's Makefile from the
|
|
19
|
+
`.tbl` files with Perl; copied from the release tarball, which ships them, so a build
|
|
20
|
+
needs only a C compiler.
|
|
21
|
+
|
|
22
|
+
2. `src/state.c`, REP (`CSI n b`, repeat the previous character): with no character drawn
|
|
23
|
+
yet, `combine_width` is 0 and `while (pos.col < col) pos.col += combine_width` never
|
|
24
|
+
ends, so one `ESC [ 3 b` as a terminal's first output froze the brain for good (no
|
|
25
|
+
crash, so no restart). Now REP does nothing then, as ECMA-48 leaves it undefined.
|
|
26
|
+
Found by tests/test_vterm.py on its second seed; libvterm's own screen layer hangs
|
|
27
|
+
the same way. The same loop repeated a wide character into the last column, half
|
|
28
|
+
past the row's end: libvterm's screen layer then writes through a NULL cell pointer
|
|
29
|
+
(`screen.c` putglyph, a segfault; found on seed 46). Now a character is repeated
|
|
30
|
+
only while it fits. Not reported upstream yet.
|
|
31
|
+
|
|
32
|
+
3. `src/state.c`, `on_resize`: only the bottom and right margins were clamped to the new
|
|
33
|
+
size, so a scroll region that no longer fit (`ESC [ 6 ; 10 r`, then 3 rows) ended
|
|
34
|
+
upside down, and the next line feed's `scroll()` did a `memmove` of negative size
|
|
35
|
+
(found by AddressSanitizer on tests/test_vterm.py seed 10040; before it, a heap
|
|
36
|
+
corruption). Such a region now goes back to the whole screen, as xterm resets
|
|
37
|
+
margins on a resize. Not reported upstream yet.
|
|
38
|
+
|
|
39
|
+
4. `src/state.c`, `savecursor` (restore, `ESC 8`, leaving `?1049`): the saved position
|
|
40
|
+
came back unclamped, so a cursor saved on row 4 and restored after a resize to 3 rows
|
|
41
|
+
sat off the screen; the next glyph read and wrote `lineinfo` past its end, and
|
|
42
|
+
libvterm's screen layer wrote through a NULL cell (AddressSanitizer, seed 103772
|
|
43
|
+
of a 5000-seed run). Clamped to the screen now, as xterm restores a cursor. Not
|
|
44
|
+
reported upstream yet.
|
|
45
|
+
|
|
46
|
+
Nothing else in libvterm is changed; py-screen's own C code reaches libvterm through its
|
|
47
|
+
headers and, for its own resize, the line-info arrays and allocator libvterm's state
|
|
48
|
+
expects a resize callback to replace (as `screen.c` does).
|
|
49
|
+
|
|
50
|
+
To update, keep it flat the same way: fetch the new tag, `git rm -rq deps/libvterm`,
|
|
51
|
+
`git read-tree --prefix=deps/libvterm -u <tag>^{tree}`, commit with the two trailers
|
|
52
|
+
naming the new upstream commit, then redo change 1 from that release's tarball and
|
|
53
|
+
check its sha256 against Debian's `.dsc`. (`git subtree pull --squash` also works from
|
|
54
|
+
the trailers, tested 0.3.2 → 0.3.3, but it adds a merge commit.)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
bin/
|
|
2
|
+
- contains some standalone programs
|
|
3
|
+
|
|
4
|
+
bin/unterm.c
|
|
5
|
+
- an example program using libvterm to reconstruct the final state of a
|
|
6
|
+
terminal after replaying captured input
|
|
7
|
+
|
|
8
|
+
bin/vterm-ctrl.c
|
|
9
|
+
- a helper program that emits sequences to control a running libvterm
|
|
10
|
+
terminal
|
|
11
|
+
|
|
12
|
+
bin/vterm-dump.c
|
|
13
|
+
- an example program using the parser layer of libvterm to interpret captured
|
|
14
|
+
input
|
|
15
|
+
|
|
16
|
+
CODE-MAP
|
|
17
|
+
- high-level list and description of files in the repository
|
|
18
|
+
|
|
19
|
+
CONTRIBUTING
|
|
20
|
+
- documentation explaining how developers can contribute fixes and features
|
|
21
|
+
|
|
22
|
+
doc/
|
|
23
|
+
- contains documentation
|
|
24
|
+
|
|
25
|
+
doc/seqs.txt
|
|
26
|
+
- documents the sequences recognised by the library
|
|
27
|
+
|
|
28
|
+
include/vterm.h
|
|
29
|
+
- main include file
|
|
30
|
+
|
|
31
|
+
include/vterm_keycodes.h
|
|
32
|
+
- include file containing the keyboard input keycode enumerations
|
|
33
|
+
|
|
34
|
+
LICENSE
|
|
35
|
+
- legalese
|
|
36
|
+
|
|
37
|
+
Makefile
|
|
38
|
+
- main build file
|
|
39
|
+
|
|
40
|
+
src/
|
|
41
|
+
- contains the source code for the library
|
|
42
|
+
|
|
43
|
+
src/encoding.c
|
|
44
|
+
- handles mapping ISO/IEC 2022 alternate character sets into Unicode
|
|
45
|
+
codepoints
|
|
46
|
+
|
|
47
|
+
src/keyboard.c
|
|
48
|
+
- handles sending reported keyboard events to the output stream
|
|
49
|
+
|
|
50
|
+
src/mouse.c
|
|
51
|
+
- handles sending reported mouse events to the output stream
|
|
52
|
+
|
|
53
|
+
src/parser.c
|
|
54
|
+
- parses bytes from the input stream into parser-level events
|
|
55
|
+
|
|
56
|
+
src/pen.c
|
|
57
|
+
- interprets SGR sequences and maintains current rendering attributes
|
|
58
|
+
|
|
59
|
+
src/screen.c
|
|
60
|
+
- uses state-level events to maintain a buffer of current screen contents
|
|
61
|
+
|
|
62
|
+
src/state.c
|
|
63
|
+
- follows parser-level events to keep track of the overall terminal state
|
|
64
|
+
|
|
65
|
+
src/unicode.c
|
|
66
|
+
- utility functions for Unicode and UTF-8 handling
|
|
67
|
+
|
|
68
|
+
src/vterm.c
|
|
69
|
+
- toplevel object state and miscellaneous functions
|
|
70
|
+
|
|
71
|
+
src/vterm_internal.h
|
|
72
|
+
- include file for definitions private to the library's internals
|
|
73
|
+
|
|
74
|
+
t/
|
|
75
|
+
- contains unit tests
|
|
76
|
+
|
|
77
|
+
t/harness.c
|
|
78
|
+
- standalone program to embed the library into for unit-test purposes
|
|
79
|
+
|
|
80
|
+
t/run-test.pl
|
|
81
|
+
- invokes the test harness to run a single unit test script
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
How to Contribute
|
|
2
|
+
-----------------
|
|
3
|
+
|
|
4
|
+
The main resources for this library are:
|
|
5
|
+
|
|
6
|
+
Launchpad
|
|
7
|
+
https://launchpad.net/libvterm
|
|
8
|
+
|
|
9
|
+
IRC:
|
|
10
|
+
##tty or #tickit on irc.libera.chat
|
|
11
|
+
|
|
12
|
+
Email:
|
|
13
|
+
Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Bug reports and feature requests can be sent to any of the above resources.
|
|
17
|
+
|
|
18
|
+
New features, bug patches, etc.. should in the first instance be discussed via
|
|
19
|
+
any of the resources listed above, before starting work on the actual code.
|
|
20
|
+
There may be future plans or development already in-progress that could be
|
|
21
|
+
affected so it is better to discuss the ideas first before starting work
|
|
22
|
+
actually writing any code.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
The MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
|
15
|
+
all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
THE SOFTWARE.
|