mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
* Initial plan * Phase 1: Update IConsoleDriver.Init() to return void instead of MainLoop Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 2: Remove legacy MainLoop infrastructure Co-authored-by: tig <585482+tig@users.noreply.github.com> * Complete Phase 1 and Phase 2 - All tests pass Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update deep dive docs to reflect MainLoop removal Co-authored-by: tig <585482+tig@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com>
302 lines
12 KiB
Markdown
302 lines
12 KiB
Markdown
# Cross-Platform Driver Model
|
|
|
|
## Overview
|
|
|
|
The driver model is the mechanism by which Terminal.Gui supports multiple platforms. Windows, Mac, Linux, and unit test environments are all supported through a modular, component-based architecture.
|
|
|
|
Terminal.Gui v2 uses a sophisticated driver architecture that separates concerns and enables platform-specific optimizations while maintaining a consistent API. The architecture is based on the **Component Factory** pattern and uses **multi-threading** to ensure responsive input handling.
|
|
|
|
## Available Drivers
|
|
|
|
Terminal.Gui provides console driver implementations optimized for different platforms:
|
|
|
|
- **DotNetDriver (`dotnet`)** - A cross-platform driver that uses the .NET `System.Console` API. Works on all platforms (Windows, macOS, Linux). Best for maximum compatibility.
|
|
- **WindowsDriver (`windows`)** - A Windows-optimized driver that uses native Windows Console APIs for enhanced performance and platform-specific features.
|
|
- **UnixDriver (`unix`)** - A Unix/Linux/macOS-optimized driver that uses platform-specific APIs for better integration and performance.
|
|
- **FakeDriver (`fake`)** - A mock driver designed for unit testing. Simulates console behavior without requiring a real terminal.
|
|
|
|
### Automatic Driver Selection
|
|
|
|
The appropriate driver is automatically selected based on the platform when you call `Application.Init()`:
|
|
|
|
- **Windows** (Win32NT, Win32S, Win32Windows) → `WindowsDriver`
|
|
- **Unix/Linux/macOS** → `UnixDriver`
|
|
|
|
### Explicit Driver Selection
|
|
|
|
You can explicitly specify a driver in three ways:
|
|
|
|
```csharp
|
|
// Method 1: Set ForceDriver property before Init
|
|
Application.ForceDriver = "dotnet";
|
|
Application.Init();
|
|
|
|
// Method 2: Pass driver name to Init
|
|
Application.Init(driverName: "unix");
|
|
|
|
// Method 3: Pass a custom IConsoleDriver instance
|
|
var customDriver = new MyCustomDriver();
|
|
Application.Init(driver: customDriver);
|
|
```
|
|
|
|
Valid driver names: `"dotnet"`, `"windows"`, `"unix"`, `"fake"`
|
|
|
|
## Architecture
|
|
|
|
### Component Factory Pattern
|
|
|
|
The v2 driver architecture uses the **Component Factory** pattern to create platform-specific components. Each driver has a corresponding factory:
|
|
|
|
- `NetComponentFactory` - Creates components for DotNetDriver
|
|
- `WindowsComponentFactory` - Creates components for WindowsDriver
|
|
- `UnixComponentFactory` - Creates components for UnixDriver
|
|
- `FakeComponentFactory` - Creates components for FakeDriver
|
|
|
|
### Core Components
|
|
|
|
Each driver is composed of specialized components, each with a single responsibility:
|
|
|
|
#### IConsoleInput<T>
|
|
Reads raw console input events from the terminal. The generic type `T` represents the platform-specific input type:
|
|
- `ConsoleKeyInfo` for DotNetDriver and FakeDriver
|
|
- `WindowsConsole.InputRecord` for WindowsDriver
|
|
- `char` for UnixDriver
|
|
|
|
Runs on a dedicated input thread to avoid blocking the UI.
|
|
|
|
#### IConsoleOutput
|
|
Renders the output buffer to the terminal. Handles:
|
|
- Writing text and ANSI escape sequences
|
|
- Setting cursor position
|
|
- Managing cursor visibility
|
|
- Detecting terminal window size
|
|
|
|
#### IInputProcessor
|
|
Translates raw console input into Terminal.Gui events:
|
|
- Converts raw input to `Key` events (handles keyboard input)
|
|
- Parses ANSI escape sequences (mouse events, special keys)
|
|
- Generates `MouseEventArgs` for mouse input
|
|
- Handles platform-specific key mappings
|
|
|
|
#### IOutputBuffer
|
|
Manages the screen buffer and drawing operations:
|
|
- Maintains the `Contents` array (what should be displayed)
|
|
- Provides methods like `AddRune()`, `AddStr()`, `Move()`, `FillRect()`
|
|
- Handles clipping regions
|
|
- Tracks dirty regions for efficient rendering
|
|
|
|
#### IWindowSizeMonitor
|
|
Detects terminal size changes and raises `SizeChanged` events when the terminal is resized.
|
|
|
|
#### ConsoleDriverFacade<T>
|
|
A unified facade that implements `IConsoleDriver` and coordinates all the components. This is what gets assigned to `Application.Driver`.
|
|
|
|
### Threading Model
|
|
|
|
The driver architecture employs a **multi-threaded design** for optimal responsiveness:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ ApplicationImpl.Init() │
|
|
│ Creates MainLoopCoordinator<T> with │
|
|
│ ComponentFactory<T> │
|
|
└────────────────┬────────────────────────────┘
|
|
│
|
|
├──────────────────┬───────────────────┐
|
|
│ │ │
|
|
┌────────▼────────┐ ┌──────▼─────────┐ ┌──────▼──────────┐
|
|
│ Input Thread │ │ Main UI Thread│ │ ConsoleDriver │
|
|
│ │ │ │ │ Facade │
|
|
│ IConsoleInput │ │ ApplicationMain│ │ │
|
|
│ reads console │ │ Loop processes │ │ Coordinates all │
|
|
│ input async │ │ events, layout,│ │ components │
|
|
│ into queue │ │ and rendering │ │ │
|
|
└─────────────────┘ └────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
- **Input Thread**: Started by `MainLoopCoordinator`, runs `IConsoleInput.Run()` which continuously reads console input and queues it into a thread-safe `ConcurrentQueue<T>`.
|
|
|
|
- **Main UI Thread**: Runs `ApplicationMainLoop.Iteration()` which:
|
|
1. Processes input from the queue via `IInputProcessor`
|
|
2. Executes timeout callbacks
|
|
3. Checks for UI changes (layout/drawing)
|
|
4. Renders updates via `IConsoleOutput`
|
|
|
|
This separation ensures that input is never lost and the UI remains responsive during intensive operations.
|
|
|
|
### Initialization Flow
|
|
|
|
When you call `Application.Init()`:
|
|
|
|
1. **ApplicationImpl.Init()** is invoked
|
|
2. Creates a `MainLoopCoordinator<T>` with the appropriate `ComponentFactory<T>`
|
|
3. **MainLoopCoordinator.StartAsync()** begins:
|
|
- Starts the input thread which creates `IConsoleInput<T>`
|
|
- Initializes the main UI loop which creates `IConsoleOutput`
|
|
- Creates `ConsoleDriverFacade<T>` and assigns to `Application.Driver`
|
|
- Waits for both threads to be ready
|
|
4. Returns control to the application
|
|
|
|
### Shutdown Flow
|
|
|
|
When `Application.Shutdown()` is called:
|
|
|
|
1. Cancellation token is triggered
|
|
2. Input thread exits its read loop
|
|
3. `IConsoleOutput` is disposed
|
|
4. Main thread waits for input thread to complete
|
|
5. All resources are cleaned up
|
|
|
|
## Component Interfaces
|
|
|
|
### IConsoleDriver
|
|
|
|
The main driver interface that the framework uses internally. Provides:
|
|
|
|
- **Screen Management**: `Screen`, `Cols`, `Rows`, `Contents`
|
|
- **Drawing Operations**: `AddRune()`, `AddStr()`, `Move()`, `FillRect()`
|
|
- **Cursor Management**: `SetCursorVisibility()`, `UpdateCursor()`
|
|
- **Attribute Management**: `CurrentAttribute`, `SetAttribute()`, `MakeColor()`
|
|
- **Clipping**: `Clip` property
|
|
- **Events**: `KeyDown`, `KeyUp`, `MouseEvent`, `SizeChanged`
|
|
- **Platform Features**: `SupportsTrueColor`, `Force16Colors`, `Clipboard`
|
|
|
|
**Note:** The driver is internal to Terminal.Gui. View classes should not access `Driver` directly. Instead:
|
|
- Use @Terminal.Gui.App.Application.Screen to get screen dimensions
|
|
- Use @Terminal.Gui.ViewBase.View.Move for positioning (with viewport-relative coordinates)
|
|
- Use @Terminal.Gui.ViewBase.View.AddRune and @Terminal.Gui.ViewBase.View.AddStr for drawing
|
|
- ViewBase infrastructure classes (in `Terminal.Gui/ViewBase/`) can access Driver when needed for framework implementation
|
|
|
|
### IConsoleDriverFacade
|
|
|
|
Extended interface for v2 drivers that exposes the internal components:
|
|
|
|
- `IInputProcessor InputProcessor`
|
|
- `IOutputBuffer OutputBuffer`
|
|
- `IWindowSizeMonitor WindowSizeMonitor`
|
|
|
|
This interface allows advanced scenarios and testing.
|
|
|
|
## Platform-Specific Details
|
|
|
|
### DotNetDriver (NetComponentFactory)
|
|
|
|
- Uses `System.Console` for all I/O operations
|
|
- Input: Reads `ConsoleKeyInfo` via `Console.ReadKey()`
|
|
- Output: Uses `Console.Write()` and ANSI escape sequences
|
|
- Works on all platforms but may have limited features
|
|
- Best for maximum compatibility and simple applications
|
|
|
|
### WindowsDriver (WindowsComponentFactory)
|
|
|
|
- Uses Windows Console API via P/Invoke
|
|
- Input: Reads `InputRecord` structs via `ReadConsoleInput`
|
|
- Output: Uses Windows Console API for optimal performance
|
|
- Supports Windows-specific features and better performance
|
|
- Automatically selected on Windows platforms
|
|
|
|
#### Visual Studio Debug Console Support
|
|
|
|
When running in Visual Studio's debug console (`VSDebugConsole.exe`), WindowsDriver detects the `VSAPPIDNAME` environment variable and automatically adjusts its behavior:
|
|
|
|
- Disables the alternative screen buffer (which is not supported in VS debug console)
|
|
- Preserves the original console colors on startup
|
|
- Restores the original colors and clears the screen on shutdown
|
|
|
|
This ensures Terminal.Gui applications can be debugged directly in Visual Studio without rendering issues.
|
|
|
|
### UnixDriver (UnixComponentFactory)
|
|
|
|
- Uses Unix/Linux terminal APIs
|
|
- Input: Reads raw `char` data from terminal
|
|
- Output: Uses ANSI escape sequences
|
|
- Supports Unix-specific features
|
|
- Automatically selected on Unix/Linux/macOS platforms
|
|
|
|
### FakeDriver (FakeComponentFactory)
|
|
|
|
- Simulates console behavior for unit testing
|
|
- Uses `FakeConsole` for all operations
|
|
- Allows injection of predefined input
|
|
- Captures output for verification
|
|
- Always used when `Application._forceFakeConsole` is true
|
|
|
|
## Example: Checking Driver Capabilities
|
|
|
|
```csharp
|
|
Application.Init();
|
|
|
|
// The driver is internal - access through Application properties
|
|
// Check screen dimensions
|
|
var screenWidth = Application.Screen.Width;
|
|
var screenHeight = Application.Screen.Height;
|
|
|
|
// Check if 24-bit color is supported
|
|
bool supportsTrueColor = Application.Driver?.SupportsTrueColor ?? false;
|
|
|
|
// Access advanced components (for framework/infrastructure code only)
|
|
if (Application.Driver is IConsoleDriverFacade facade)
|
|
{
|
|
// Access individual components for advanced scenarios
|
|
IInputProcessor inputProcessor = facade.InputProcessor;
|
|
IOutputBuffer outputBuffer = facade.OutputBuffer;
|
|
IWindowSizeMonitor sizeMonitor = facade.WindowSizeMonitor;
|
|
|
|
// Use components for advanced scenarios
|
|
sizeMonitor.SizeChanging += (s, e) =>
|
|
{
|
|
Console.WriteLine($"Terminal resized to {e.Size}");
|
|
};
|
|
}
|
|
```
|
|
|
|
**Important:** View subclasses should not access `Application.Driver`. Use the View APIs instead:
|
|
- `View.Move(col, row)` for positioning
|
|
- `View.AddRune()` and `View.AddStr()` for drawing
|
|
- `Application.Screen` for screen dimensions
|
|
|
|
## Custom Drivers
|
|
|
|
To create a custom driver, implement `IComponentFactory<T>`:
|
|
|
|
```csharp
|
|
public class MyComponentFactory : ComponentFactory<MyInputType>
|
|
{
|
|
public override IConsoleInput<MyInputType> CreateInput()
|
|
{
|
|
return new MyConsoleInput();
|
|
}
|
|
|
|
public override IConsoleOutput CreateOutput()
|
|
{
|
|
return new MyConsoleOutput();
|
|
}
|
|
|
|
public override IInputProcessor CreateInputProcessor(
|
|
ConcurrentQueue<MyInputType> inputBuffer)
|
|
{
|
|
return new MyInputProcessor(inputBuffer);
|
|
}
|
|
}
|
|
```
|
|
|
|
Then use it:
|
|
|
|
```csharp
|
|
ApplicationImpl.ChangeComponentFactory(new MyComponentFactory());
|
|
Application.Init();
|
|
```
|
|
|
|
## Legacy Drivers
|
|
|
|
Terminal.Gui v1 drivers that implement `IConsoleDriver` but not `IConsoleDriverFacade` are still supported through a legacy compatibility layer. However, they do not benefit from the v2 architecture improvements (multi-threading, component separation, etc.).
|
|
|
|
**Note**: The legacy `MainLoop` infrastructure (including the `MainLoop` class, `IMainLoopDriver` interface, and `FakeMainLoop`) has been removed in favor of the modern architecture. All drivers now use the `MainLoopCoordinator` and `ApplicationMainLoop` system exclusively.
|
|
|
|
## See Also
|
|
|
|
- @Terminal.Gui.Drivers - API Reference
|
|
- @Terminal.Gui.App.Application - Application class
|
|
- @Terminal.Gui.App.ApplicationImpl - Application implementation
|
|
- @Terminal.Gui.App.MainLoopCoordinator`1 - Main loop coordination
|