You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/controls/TerminalControl.md
+51-29Lines changed: 51 additions & 29 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,22 +2,26 @@
2
2
3
3
PTY-backed terminal emulator control that embeds a fully interactive shell or process inside any window.
4
4
5
-
> **Platform:** Linux only. The control throws `PlatformNotSupportedException` on other operating systems.
5
+
> **Platform:** Linux and Windows 10 1809+ (build 17763). The control throws `PlatformNotSupportedException` on other operating systems.
6
6
7
7
## Overview
8
8
9
9
`TerminalControl` opens a real pseudo-terminal (PTY), spawns a child process inside it, and renders the VT100/xterm-256color screen directly into the SharpConsoleUI buffer. Keyboard and mouse input are forwarded to the process. The terminal resizes automatically when the window is resized.
10
10
11
-
## Critical Setup Requirement
11
+
On **Linux** the control uses `openpty` and an in-process shim. On **Windows** it uses the ConPTY API (`CreatePseudoConsole`) introduced in Windows 10 1809.
12
12
13
-
`TerminalControl` relies on an in-process PTY shim — the host executable re-launches itself as the slave-side process. **You must add the following as the very first line of your `Main` method**, before any UI initialisation:
13
+
## Critical Setup Requirement (Linux only)
14
+
15
+
On Linux, `TerminalControl` relies on an in-process PTY shim — the host executable re-launches itself as the slave-side process. **You must add the following as the very first line of your `Main` method**, before any UI initialisation:
14
16
15
17
```csharp
16
18
// Program.cs
17
19
if (SharpConsoleUI.PtyShim.RunIfShim(args)) return127;
18
20
```
19
21
20
-
Without this line the PTY will open but the child process will never start, leaving the terminal blank.
22
+
Without this line on Linux the PTY will open but the child process will never start, leaving the terminal blank.
23
+
24
+
On **Windows** no shim is required. `PtyShim.RunIfShim` is a no-op on Windows (returns `false` immediately), so the call is safe to keep in cross-platform code.
21
25
22
26
## Properties
23
27
@@ -37,14 +41,16 @@ Without this line the PTY will open but the child process will never start, leav
37
41
### Using the Builder — Quick Open (Recommended)
38
42
39
43
```csharp
40
-
// Open a bash terminal in a new auto-sized window
44
+
// Open the default shell in a new auto-sized window
|`WithExe(string exe)`| Set the executable to launch (default: `/bin/bash`) |
94
+
|`WithExe(string exe)`| Set the executable to launch (default: `bash` on Linux, `cmd.exe` on Windows) |
89
95
|`WithArgs(params string[] args)`| Pass arguments to the executable |
90
96
|`Build()`| Create the `TerminalControl` (PTY is opened immediately) |
91
97
|`Open(ConsoleWindowSystem ws, int? width, int? height)`| Build and open in a new centered window |
@@ -131,24 +137,52 @@ Both X10 and SGR (1006) mouse encoding protocols are supported, selected by what
131
137
132
138
## Lifecycle
133
139
134
-
1.**Constructor** — PTY is opened, shim process is started, read thread begins.
140
+
1.**Constructor** — PTY backend is opened, child process is started, read thread begins.
135
141
2.**PaintDOM** — Terminal is resized to match the layout bounds on the first paint and on every subsequent resize.
136
-
3.**Child process exits** — Read thread detects EOF, closes the master fd, waits for the shim to exit, then closes the containing window automatically.
137
-
4.**Dispose** — Closes the master fd, which signals the child process to terminate.
142
+
3.**Child process exits** — Read thread detects EOF, disposes the PTY backend, then closes the containing window automatically.
143
+
4.**Dispose** — Disposes the PTY backend, which signals the child process to terminate (closes the master fd on Linux; closes the ConPTY and pipes on Windows).
144
+
145
+
## How It Works
146
+
147
+
### Linux — openpty + in-process shim
148
+
149
+
1.`PtyNative.Open()` creates a PTY master/slave fd pair.
150
+
2. The host re-launches **itself** (`Environment.ProcessPath`) with `--pty-shim <slaveFd> <exe> [args]`.
151
+
3.`PtyShim.RunIfShim` detects these arguments, calls `setsid`/`ioctl(TIOCSCTTY)`, redirects stdin/stdout/stderr to the slave fd, and `execvp`s the target — replacing the shim process entirely.
152
+
4. The original process reads from the master fd in a background thread, feeds bytes to the `VT100Machine`, and calls `Invalidate` after each read.
153
+
5. Keyboard/mouse input is written back to the master fd as escape sequences.
154
+
155
+
This design requires no external binaries and works with any process that supports a TTY.
156
+
157
+
### Windows — ConPTY (Windows 10 1809+)
158
+
159
+
1. Two anonymous pipes are created: one for keyboard input, one for terminal output.
160
+
2.`CreatePseudoConsole` (kernel32) is called with those pipe handles, creating a Windows pseudoconsole.
161
+
3.`CreateProcess` is called with `EXTENDED_STARTUPINFO_PRESENT` and the `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE` attribute, connecting the child's console I/O to the ConPTY.
162
+
4. The original process reads from the output pipe in a background thread, feeds bytes to the `VT100Machine`, and calls `Invalidate`.
163
+
5. Keyboard/mouse input is written to the input pipe as escape sequences.
164
+
165
+
No shim or self-relaunch is needed on Windows.
138
166
139
167
## Examples
140
168
141
-
### Default Bash Terminal
169
+
### Default Shell (cross-platform)
142
170
143
171
```csharp
144
-
// Program.cs — REQUIRED before any UI code
172
+
// Program.cs — required on Linux; safe no-op on Windows
145
173
if (SharpConsoleUI.PtyShim.RunIfShim(args)) return127;
`TerminalControl` uses a **in-process PTY shim** pattern to avoid the need for a separate helper binary:
205
-
206
-
1. The host process calls `PtyNative.Open()` to create a PTY master/slave pair.
207
-
2. It re-launches **itself** (`Environment.ProcessPath`) with `--pty-shim <slaveFd> <exe> [args]` as arguments.
208
-
3.`PtyShim.RunIfShim` detects these arguments, calls `setsid`/`ioctl(TIOCSCTTY)`, redirects stdin/stdout/stderr to the slave fd, and `exec`s the target executable — replacing the shim process entirely.
209
-
4. The original process reads from the master fd in a background thread, feeds bytes to the `VT100Machine`, and calls `Invalidate` after each read so the UI repaints.
210
-
5. Keyboard/mouse input is written back to the master fd as escape sequences.
211
-
212
-
This design requires no external binaries and works with any process that supports a TTY.
213
-
214
236
## Best Practices
215
237
216
-
-**Always add `PtyShim.RunIfShim(args)` first** — it must run before any console or UI initialisation.
238
+
-**Linux: always add `PtyShim.RunIfShim(args)` first** — it must run before any console or UI initialisation. The call is a safe no-op on Windows.
217
239
-**Let the window close itself** — when the child exits, the window closes automatically; do not force-close it from outside.
218
240
-**Prefer `Open()`** for simple cases; use `Build()` only when you need custom window configuration.
219
241
-**Do not set `HasFocus = false`** while the terminal is the only control — keyboard input will stop being forwarded.
0 commit comments