Skip to content

Commit aa1cd42

Browse files
Add Binary Paste Mode (DEC mode 2033) for image/binary clipboard paste
Introduce a new VT protocol extension that allows terminal applications to receive binary clipboard data (e.g., images) with MIME type metadata via DCS sequences. Applications opt in via DECSET 2033; feature detection uses DECRQM (CSI ? 2033 $ p) to distinguish "supported but disabled" from "not supported". Protocol: DCS 2033 ; <size> b <mime-type> ; <base64-data> ST - DECMode 2033: opt-in mode with standard DECSET/DECRST/DECRQM support - InputGenerator::generateBinaryPaste() produces DCS binary paste sequences - Terminal::sendBinaryPaste() routes binary data through the input pipeline - TerminalSession::pasteFromClipboard() inspects clipboard MIME types when mode is enabled, with priority: png > jpeg > gif > bmp > svg+xml - Size limits: 10 MB hard, 5 MB soft (user permission prompt) - Size validation: decoded payload must match declared Ps parameter - VT sequence parameters widened from uint16_t to uint32_t to support payload sizes beyond 64 KB (max ~4 GB) - 7 E2E tests covering mode control, DECRQM, DCS generation, and reset - VT extension spec at docs/vt-extensions/binary-paste.md with adoption table - Example app: examples/watch-clipboard-paste.cpp Signed-off-by: Christian Parpart <christian@parpart.family>
1 parent d9b06a9 commit aa1cd42

File tree

20 files changed

+1183
-15
lines changed

20 files changed

+1183
-15
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
1+
AAAANSUh
12
ABCDEFGHIX
23
ABCXDEFGHI
34
ABCXYDEFGH
45
ABCXZEFGHI
56
BBBBB
7+
bcimage
8+
bctext
9+
bdimage
10+
BINARYPASTE
611
BLval
712
BRval
813
CCCCC
14+
cimage
915
circleval
1016
ctrled
1117
dcx
1218
DDDDD
19+
DECRST
20+
DECSET
21+
dimage
1322
ellips
1423
emtpy
24+
EUg
1525
FFFFF
1626
GGGGG
1727
gitbranch
1828
gitgraph
1929
HHHHH
2030
HTP
2131
isakbm
32+
KGgo
33+
mday
2234
pseudoconsole
2335
rbong
2436
ULval
2537
unscroll
2638
URval
39+
VBORw
2740
xad

docs/vt-extensions/binary-paste.md

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Binary Paste Mode
2+
3+
Applications running inside a terminal currently have no way to receive binary clipboard data
4+
(such as images) from the user's clipboard. The traditional paste mechanism — including
5+
[Bracketed Paste Mode](https://en.wikipedia.org/wiki/Bracketed-paste) (DEC mode 2004) —
6+
only supports plain text.
7+
8+
Binary Paste Mode is a terminal protocol extension that allows the terminal emulator
9+
to deliver binary clipboard data to applications that opt in, complete with MIME type
10+
metadata and base64 encoding. This enables use cases such as pasting images into
11+
terminal-based editors, chat clients, or file managers.
12+
13+
## Feature Detection
14+
15+
An application can detect support by sending a **DECRQM** query for mode 2033:
16+
17+
```
18+
CSI ? 2033 $ p Query Binary Paste Mode
19+
```
20+
21+
Response: `CSI ? 2033 ; Ps $ y` where `Ps` indicates:
22+
23+
| `Ps` | Meaning |
24+
|------|------------------------------------------------------------------|
25+
| `1` | Mode is set (enabled) |
26+
| `2` | Mode is reset (disabled) |
27+
| `0` | Mode not recognized (terminal does not support Binary Paste) |
28+
29+
If the terminal does not recognize mode 2033, it will respond with `Ps = 0`,
30+
allowing the application to distinguish between "supported but disabled" and
31+
"not supported at all".
32+
33+
## Mode Control
34+
35+
Applications opt in via standard DECSET/DECRST sequences using **DEC private mode 2033**:
36+
37+
```
38+
CSI ? 2033 h Enable Binary Paste Mode
39+
CSI ? 2033 l Disable Binary Paste Mode (default)
40+
```
41+
42+
Binary Paste Mode is disabled by default and resets to disabled on hard reset (RIS)
43+
and soft reset (DECSTR).
44+
45+
## Sub-Command Architecture
46+
47+
All binary paste operations use a single DCS sequence format with a **sub-command
48+
character** as the first byte of the data string:
49+
50+
```
51+
DCS 2033 [; params] b <sub-cmd><payload> ST
52+
```
53+
54+
Where:
55+
56+
| Component | Description |
57+
|-----------|-------------------------------------------------------------------------|
58+
| `2033` | First parameter, matching the mode number |
59+
| `params` | Optional additional parameters (e.g., byte count for data delivery) |
60+
| `b` | DCS final character (mnemonic: *binary*) |
61+
| `sub-cmd` | First byte of data string: sub-command identifier |
62+
| `payload` | Sub-command-specific data |
63+
| `ST` | String Terminator (`ESC \`) |
64+
65+
### Sub-command Summary
66+
67+
| Sub-cmd | Direction | Purpose |
68+
|---------|----------------|---------------------------------|
69+
| `d` | Terminal → App | Data delivery (paste event) |
70+
| `c` | App → Terminal | Configure MIME type preferences |
71+
72+
## Data Delivery (`d`)
73+
74+
When Binary Paste Mode is enabled and the user initiates a paste action while the
75+
system clipboard contains a matching MIME type, the terminal sends the following DCS
76+
sequence to the application via the PTY:
77+
78+
```
79+
DCS 2033 ; Ps b d <mime-type> ; <base64-encoded-data> ST
80+
```
81+
82+
Where:
83+
84+
| Component | Description |
85+
|-------------------------|-------------------------------------------------------------------------|
86+
| `Ps` | Second parameter: byte count of the original (pre-encoding) binary data |
87+
| `d` | Sub-command: data delivery |
88+
| `<mime-type>` | MIME type of the delivered content (e.g., `image/png`) |
89+
| `<base64-encoded-data>` | Base64-encoded binary data |
90+
91+
### Example
92+
93+
A 1234-byte PNG image on the clipboard produces:
94+
95+
```
96+
ESC P 2033 ; 1234 b dimage/png ; iVBORw0KGgoAAAANSUhEUgAA... ESC \
97+
```
98+
99+
The `Ps` (size) parameter allows the application to pre-allocate a buffer before
100+
decoding the base64 payload. If absent or zero, the size is unknown.
101+
102+
### Size Validation
103+
104+
When `Ps` is present and non-zero, the application **must** verify that the decoded
105+
payload size matches the declared `Ps` value. If the sizes do not match, the
106+
application **should** discard the entire paste and treat it as a protocol error.
107+
108+
| Condition | Action |
109+
|----------------------|--------------------------------------|
110+
| `Ps` absent or zero | Accept (size unknown, no validation) |
111+
| Decoded size == `Ps` | Accept |
112+
| Decoded size != `Ps` | Discard and report error |
113+
114+
## MIME Preference Configuration (`c`)
115+
116+
Applications can configure which MIME types they accept and their priority order
117+
by sending:
118+
119+
```
120+
DCS 2033 b c <mime-list> ST
121+
```
122+
123+
Where `<mime-list>` is a **comma-separated** list of MIME types in priority order
124+
(highest priority first):
125+
126+
```
127+
ESC P 2033 b cimage/png,image/svg+xml,text/html ESC \
128+
```
129+
130+
### Behavior
131+
132+
- **Any MIME type is valid**: not limited to `image/*` types. Applications may
133+
request `text/html`, `application/json`, `text/plain`, etc.
134+
- **Priority order**: the terminal checks the system clipboard for each listed
135+
type in order and delivers the first match via the `d` sub-command.
136+
- **Default fallback**: if no preferences are configured, the terminal uses its
137+
built-in default list (see Terminal Defaults below).
138+
- **Empty payload**: sending `DCS 2033 b c ST` (no MIME types) resets preferences
139+
to terminal defaults.
140+
- **Replacement**: sending a new configure sequence replaces all previous preferences.
141+
- **Mode gating**: the configure sequence is silently ignored if mode 2033 is not
142+
enabled.
143+
- **Cleared on reset**: preferences are cleared when mode 2033 is disabled
144+
(DECRST 2033), on soft reset (DECSTR), and on hard reset (RIS).
145+
146+
### Terminal Defaults
147+
148+
When no application preferences are configured, the terminal uses the following
149+
built-in priority list:
150+
151+
| Priority | MIME Type |
152+
|----------|-----------------|
153+
| 1 | `image/png` |
154+
| 2 | `image/jpeg` |
155+
| 3 | `image/gif` |
156+
| 4 | `image/bmp` |
157+
| 5 | `image/svg+xml` |
158+
159+
### Separator Choice
160+
161+
Commas (`,`) are used to separate MIME types instead of semicolons because:
162+
- Semicolons (`;`) are already used in the data delivery sub-command to separate
163+
the MIME type from the base64 payload.
164+
- Commas align with the HTTP `Accept` header convention for MIME type lists.
165+
166+
## Interaction with Bracketed Paste Mode
167+
168+
Binary Paste Mode is independent of Bracketed Paste Mode (2004). Both can be
169+
active simultaneously. When both are active and the clipboard contains a matching
170+
MIME type, the DCS binary paste sequence takes precedence:
171+
172+
| Mode 2004 | Mode 2033 | Has Match | Behavior |
173+
|-----------|-----------|-----------|----------------------------|
174+
| off | off | any | Normal text paste |
175+
| on | off | any | Bracketed text paste |
176+
| off | on | yes | DCS binary paste |
177+
| on | on | yes | DCS binary paste |
178+
| any | on | no | Fall through to text paste |
179+
180+
Only one representation is sent per paste action — the terminal never sends both
181+
a DCS binary paste and a bracketed text paste for the same clipboard content.
182+
183+
**Note:** if `text/plain` appears in the application's MIME preference list, it
184+
will be delivered via the DCS binary paste format (the application explicitly opted
185+
into receiving it through the binary paste protocol).
186+
187+
## Size Limits
188+
189+
Terminals implementing this extension may enforce size limits on binary paste data:
190+
191+
- **Hard limit**: Payloads exceeding the limit are silently dropped (no DCS sent).
192+
Recommended: 10 MB pre-encoding.
193+
- **Soft limit**: Payloads between the soft and hard limit may trigger a user
194+
confirmation prompt. Recommended: 5 MB pre-encoding.
195+
196+
The base64 encoding inflates the wire size by approximately 33%.
197+
198+
## Future Extensions
199+
200+
The following sub-commands are reserved for future use:
201+
202+
| Sub-cmd | Direction | Purpose |
203+
|---------|----------------|---------------------------------------------|
204+
| `r` | App → Terminal | Request clipboard contents (clipboard read) |
205+
| `?` | Terminal → App | Report available MIME types |
206+
207+
Clipboard read would require a permission model to prevent unauthorized clipboard
208+
access by applications.
209+
210+
Multi-MIME delivery (sending all matching types in a single paste event with an
211+
end-of-batch marker `DCS 2033 ; 0 b d ST`) is also under consideration.
212+
213+
## Adoption State
214+
215+
| Support | Terminal/Toolkit/App | Notes |
216+
|----------|----------------------|------------------------------------|
217+
|| Contour | since `0.6.3` (initial prototype) |
218+
| not yet | Kitty | |
219+
| not yet | WezTerm | |
220+
| not yet | Ghostty | |
221+
| not yet | foot | |
222+
| not yet | tmux | |
223+
| ... | ... | |
224+
225+
If your project adds support for this feature, please
226+
[open an issue](https://github.com/contour-terminal/contour/issues) or submit a PR
227+
so we can update this table.
228+
229+
## Reference
230+
231+
- [Bracketed Paste Mode](https://en.wikipedia.org/wiki/Bracketed-paste) — DEC mode 2004, text-only predecessor
232+
- [OSC 52](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands) — clipboard read/write via escape sequences (text/base64)
233+
- [Kitty Clipboard Protocol](https://sw.kovidgoyal.net/kitty/clipboard/) — full bidirectional clipboard protocol (OSC 5522)
234+
- [DECRQM (Request Mode)](https://vt100.net/docs/vt510-rm/DECRQM.html) — mode support query

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ if(LIBTERMINAL_TESTING)
33
add_executable(watch-mouse-events watch-mouse-events.cpp)
44
target_link_libraries(watch-mouse-events vtbackend)
55

6+
add_executable(watch-clipboard-paste watch-clipboard-paste.cpp)
7+
target_link_libraries(watch-clipboard-paste vtbackend)
8+
69
add_executable(detect-dark-light-mode detect-dark-light-mode.cpp)
710
endif()
811
endif()

0 commit comments

Comments
 (0)