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.
Terminal.Gui provides console driver implementations optimized for different platforms:
dotnet) - A cross-platform driver that uses the .NET System.Console API. Works on all platforms (Windows, macOS, Linux). Best for maximum compatibility.windows) - A Windows-optimized driver that uses native Windows Console APIs for enhanced performance and platform-specific features.unix) - A Unix/Linux/macOS-optimized driver that uses platform-specific APIs for better integration and performance.fake) - A mock driver designed for unit testing. Simulates console behavior without requiring a real terminal.The appropriate driver is automatically selected based on the platform when you call Application.Init():
WindowsDriverUnixDriverYou can explicitly specify a driver in several ways:
Method 1: Set ForceDriver using Configuration Manager
{
"ForceDriver": "fake"
}
Method 2: Pass driver name to Init
Application.Init(driverName: "unix");
Method 3: Set ForceDriver on instance
using (IApplication app = Application.Create())
{
app.ForceDriver = "fake";
app.Init();
}
Valid driver names: "dotnet", "windows", "unix", "fake"
The ForceDriver property is a configuration property marked with [ConfigurationProperty], which means:
config.json)ForceDriverChanged eventIt persists across application instances when using the static Application class
// Subscribe to driver changes
Application.ForceDriverChanged += (sender, e) =>
{
Console.WriteLine($"Driver changed: {e.OldValue} → {e.NewValue}");
};
// Change driver
Application.ForceDriver = "fake";
Use GetDriverTypes() to discover which drivers are available at runtime:
(List<Type?> driverTypes, List<string?> driverNames) = Application.GetDriverTypes();
Console.WriteLine("Available drivers:");
foreach (string? name in driverNames)
{
Console.WriteLine($" - {name}");
}
// Output:
// Available drivers:
// - dotnet
// - windows
// - unix
// - fake
Note: GetDriverTypes() uses reflection to discover driver implementations and is marked with [RequiresUnreferencedCode("AOT")] and [Obsolete] as part of the legacy static API.
The v2 driver architecture uses the Component Factory pattern to create platform-specific components. Each driver has a corresponding factory:
NetComponentFactory - Creates components for DotNetDriverWindowsComponentFactory - Creates components for WindowsDriverUnixComponentFactory - Creates components for UnixDriverFakeComponentFactory - Creates components for FakeDriverEach driver is composed of specialized components, each with a single responsibility:
Reads raw console input events from the terminal. The generic type T represents the platform-specific input type:
ConsoleKeyInfo for DotNetDriver and FakeDriverWindowsConsole.InputRecord for WindowsDriverchar for UnixDriverRuns on a dedicated input thread to avoid blocking the UI.
Renders the output buffer to the terminal. Handles:
Translates raw console input into Terminal.Gui events:
Key events (handles keyboard input)MouseEventArgs for mouse inputManages the screen buffer and drawing operations:
Contents array (what should be displayed)AddRune(), AddStr(), Move(), FillRect()Detects terminal size changes and raises SizeChanged events when the terminal is resized.
A unified facade that implements IDriver and coordinates all the components. This is what gets assigned to Application.Driver.
The driver architecture employs a multi-threaded design for optimal responsiveness:
┌─────────────────────────────────────────────┐
│ IApplication.Init() │
│ Creates MainLoopCoordinator<T> with │
│ ComponentFactory<T> │
└────────────────┬────────────────────────────┘
│
├──────────────────┬───────────────────┐
│ │ │
┌────────▼────────┐ ┌──────▼─────────┐ ┌──────▼──────────┐
│ Input Thread │ │ Main UI Thread│ │ Driver │
│ │ │ │ │ Facade │
│ IInput │ │ ApplicationMain│ │ │
│ reads console │ │ Loop processes │ │ Coordinates all │
│ input async │ │ events, layout,│ │ components │
│ into queue │ │ and rendering │ │ │
└─────────────────┘ └────────────────┘ └─────────────────┘
Input Thread: Started by MainLoopCoordinator, runs IInput.Run() which continuously reads console input and queues it into a thread-safe ConcurrentQueue<T>.
Main UI Thread: Runs ApplicationMainLoop.Iteration() which:
IInputProcessorIOutputThis separation ensures that input is never lost and the UI remains responsive during intensive operations.
When you call Application.Init():
MainLoopCoordinator<T> with the appropriate ComponentFactory<T>IInput<T>IOutputDriverFacade<T> and assigns to IApplication.DriverWhen IApplication.Shutdown() is called:
IOutput is disposedThe main driver interface that the framework uses internally. IDriver is organized into logical regions:
Init(), Refresh(), End() - Core lifecycle methodsGetName(), GetVersionInfo() - Driver identificationSuspend() - Platform-specific suspend supportInputProcessor - Processes input into Terminal.Gui eventsOutputBuffer - Manages screen buffer stateSizeMonitor - Detects terminal size changesClipboard - OS clipboard integrationScreen, Cols, Rows, Left, Top - Screen dimensionsSetScreenSize(), SizeChanged - Size managementSupportsTrueColor - 24-bit color capabilityForce16Colors - Force 16-color modeContents - Screen buffer arrayClip - Clipping regionClearContents(), ClearedContents - Buffer managementCol, Row, CurrentAttribute - Drawing stateMove(), AddRune(), AddStr(), FillRect() - Drawing operationsSetAttribute(), GetAttribute() - Attribute managementWriteRaw(), GetSixels() - Raw output and graphicsRefresh(), ToString(), ToAnsi() - Output renderingUpdateCursor() - Position cursorGetCursorVisibility(), SetCursorVisibility() - Visibility managementKeyDown, KeyUp, MouseEvent - Input eventsEnqueueKeyEvent() - Test supportQueueAnsiRequest() - ANSI request handlingNote: The driver is internal to Terminal.Gui. View classes should not access Driver directly. Instead:
Terminal.Gui/ViewBase/) can access Driver when needed for framework implementationThe driver selection logic in ApplicationImpl.Driver.cs prioritizes component factory type over the driver name parameter:
IComponentFactory is already set, it determines the driverdriverName parameter to Init() is checked nextForceDriver configuration property is evaluatedWindowsDriverUnixDriverDotNetDriver (fallback)This prioritization ensures flexibility while maintaining deterministic behavior.
System.Console for all I/O operationsConsoleKeyInfo via Console.ReadKey()Console.Write() and ANSI escape sequencesInputRecord structs via ReadConsoleInputWhen running in Visual Studio's debug console (VSDebugConsole.exe), WindowsDriver detects the VSAPPIDNAME environment variable and automatically adjusts its behavior:
This ensures Terminal.Gui applications can be debugged directly in Visual Studio without rendering issues.
char data from terminalFakeConsole for all operationsIApplication.ForceDriver is fakeImportant: View subclasses should not access Application.Driver. Use the View APIs instead:
View.Move(col, row) for positioningView.AddRune() and View.AddStr() for drawingView.App.Screen for screen dimensions