TerminalWidget
Embed child terminal sessions within your TUI application.
Overview
The TerminalWidget displays the output of an embedded terminal session, such as a bash shell, PowerShell, or any PTY-based process. This enables building terminal multiplexers, IDE-like environments, or any application that needs to host child terminals.
Key Concept
The TerminalWidget connects to a TerminalWidgetHandle which provides the screen buffer from a running terminal session. The handle is created using Hex1bTerminalBuilder.WithTerminalWidget().
Live Demo
This interactive demo shows multiple embedded terminals with menu-driven switching and restart functionality. Try typing help for available commands, or colors for a color test:
dotnet runDiagnostic Shell
The live demo uses the built-in diagnostic shell which simulates a terminal environment without requiring PTY infrastructure. For real applications, you'd use .WithPtyProcess("bash") or similar.
Basic Usage
Create an embedded terminal and display it in your TUI:
using Hex1b;
// Create a terminal with an embedded bash session
var bashTerminal = Hex1bTerminal.CreateBuilder()
.WithPtyProcess("bash", "--norc")
.WithTerminalWidget(out var bashHandle)
.Build();
// Start the child terminal in the background
_ = bashTerminal.RunAsync();
// Create the main TUI app with the embedded terminal
await using var displayTerminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx =>
ctx.Border(
ctx.Terminal(bashHandle),
title: "Embedded Bash Terminal"
))
.Build();
await displayTerminal.RunAsync();The key steps are:
- Create the child terminal using
Hex1bTerminal.CreateBuilder()with.WithTerminalWidget(out var handle) - Start the child terminal with
RunAsync()in the background - Display the terminal using
ctx.Terminal(handle)in your widget tree
Multiple Terminals
You can embed multiple terminal sessions and arrange them using layout widgets:
using Hex1b;
using Hex1b.Input;
// Create multiple embedded terminals
var bash = Hex1bTerminal.CreateBuilder()
.WithPtyProcess("bash", "--norc")
.WithTerminalWidget(out var bashHandle)
.Build();
var pwsh = Hex1bTerminal.CreateBuilder()
.WithPtyProcess("pwsh", "-NoProfile", "-NoLogo")
.WithTerminalWidget(out var pwshHandle)
.Build();
// Start both terminals
_ = bash.RunAsync();
_ = pwsh.RunAsync();
// Display side-by-side using HStack
await using var displayTerminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx =>
ctx.HStack(h => [
h.Border(
h.Terminal(bashHandle),
title: "Bash"
).Fill(),
h.Border(
h.Terminal(pwshHandle),
title: "PowerShell"
).Fill()
]))
.Build();
await displayTerminal.RunAsync();Fallback Widget
When a terminal process exits, you can display a fallback widget using WhenNotRunning():
using Hex1b;
var terminal = Hex1bTerminal.CreateBuilder()
.WithPtyProcess("bash", "--norc")
.WithTerminalWidget(out var bashHandle)
.Build();
Hex1bApp? app = null;
void RestartTerminal()
{
bashHandle.Reset();
_ = terminal.RunAsync();
app?.Invalidate();
}
_ = terminal.RunAsync();
await using var displayTerminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((hex1bApp, options) =>
{
app = hex1bApp;
return ctx => ctx.Border(
ctx.Terminal(bashHandle)
.WhenNotRunning(args => ctx.VStack(v => [
v.Text(""),
v.Align(Alignment.Center, v.VStack(center => [
center.Text($"Terminal exited with code {args.ExitCode ?? 0}"),
center.Text(""),
center.HStack(buttons => [
buttons.Button("Restart").OnClick(_ => RestartTerminal()),
buttons.Text(" "),
buttons.Button("Close").OnClick(_ => hex1bApp.RequestStop())
])
]))
])),
title: "Terminal with Fallback"
);
})
.Build();
await displayTerminal.RunAsync();The WhenNotRunning callback receives a TerminalNotRunningArgs object containing:
| Property | Type | Description |
|---|---|---|
Handle | TerminalWidgetHandle | The terminal handle |
State | TerminalState | Current state (NotStarted or Completed) |
ExitCode | int? | Exit code if completed, null otherwise |
Terminal States
The TerminalWidgetHandle tracks the lifecycle of the child process:
using Hex1b;
// Handle terminal lifecycle states
bashHandle.StateChanged += state =>
{
switch (state)
{
case TerminalState.NotStarted:
Console.WriteLine("Terminal not started yet");
break;
case TerminalState.Running:
Console.WriteLine("Terminal is running");
break;
case TerminalState.Completed:
Console.WriteLine($"Terminal exited with code {bashHandle.ExitCode}");
break;
}
};
| State | Description |
|---|---|
NotStarted | Terminal created but not yet started |
Running | Terminal process is actively running |
Completed | Terminal process has exited |
Window Title Support
Child processes can set the terminal title using OSC escape sequences. Subscribe to title changes:
using Hex1b;
// Subscribe to title changes from the child terminal
bashHandle.WindowTitleChanged += title =>
{
// Update your UI to reflect the new title
app.Invalidate();
};
// Use the title in your widget tree
ctx.Border(
ctx.Terminal(bashHandle),
title: bashHandle.WindowTitle ?? "Terminal"
);
The handle provides:
WindowTitle- Current window title (OSC 0/2)IconName- Current icon name (OSC 0/1)WindowTitleChangedevent - Fired when title changesIconNameChangedevent - Fired when icon name changes
Input Handling
When a TerminalWidget has focus:
- Keyboard input is automatically forwarded to the child process
- Mouse events are forwarded if the child process has enabled mouse tracking
- Focus is captured while the terminal is running
Focus Behavior
When the child terminal exits, focus is released and can move to the fallback widget's interactive elements (like buttons).
API Reference
TerminalWidget Record
public sealed record TerminalWidget(TerminalWidgetHandle Handle) : Hex1bWidgetExtension Methods
| Method | Description |
|---|---|
ctx.Terminal(handle) | Creates a TerminalWidget bound to the handle |
.WhenNotRunning(builder) | Sets a fallback widget for when the terminal is not running |
TerminalWidgetHandle Properties
| Property | Type | Description |
|---|---|---|
Width | int | Current width in columns |
Height | int | Current height in rows |
State | TerminalState | Current lifecycle state |
ExitCode | int? | Exit code if completed |
IsRunning | bool | Whether the terminal is currently running |
WindowTitle | string | Current window title from OSC sequences |
CursorX | int | Current cursor X position (0-based) |
CursorY | int | Current cursor Y position (0-based) |
CursorVisible | bool | Whether cursor is visible |
MouseTrackingEnabled | bool | Whether child has enabled mouse tracking |
TerminalWidgetHandle Methods
| Method | Description |
|---|---|
SendEventAsync(evt) | Send a key or mouse event to the child process |
Resize(width, height) | Resize the terminal buffer |
Reset() | Reset state to NotStarted (for restarting) |
GetScreenBuffer() | Get a copy of the current screen buffer |
Related
- Terminal Emulator Guide — Learn about Hex1b's terminal emulation capabilities
- Stacks — Layout multiple terminals with HStack/VStack
- Border — Add borders and titles to terminal panes