using System.Runtime.InteropServices; namespace Terminal.Gui.Drivers; /// /// Provides the main implementation of the driver abstraction layer for Terminal.Gui. /// This implementation of coordinates the interaction between input processing, output /// rendering, /// screen size monitoring, and ANSI escape sequence handling. /// /// /// /// implements , /// serving as the central coordination point for console I/O operations. It delegates functionality /// to specialized components: /// /// /// - Processes keyboard and mouse input /// - Manages the screen buffer state /// - Handles actual console output rendering /// - Manages ANSI escape sequence requests /// - Monitors terminal size changes /// /// /// This class is internal and should not be used directly by application code. /// Applications interact with drivers through the class. /// /// internal class DriverImpl : IDriver { private readonly IOutput _output; private readonly AnsiRequestScheduler _ansiRequestScheduler; private CursorVisibility _lastCursor = CursorVisibility.Default; /// /// Initializes a new instance of the class. /// /// The input processor for handling keyboard and mouse events. /// The output buffer for managing screen state. /// The output interface for rendering to the console. /// The scheduler for managing ANSI escape sequence requests. /// The monitor for tracking terminal size changes. public DriverImpl ( IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IOutput output, AnsiRequestScheduler ansiRequestScheduler, ISizeMonitor sizeMonitor ) { InputProcessor = inputProcessor; _output = output; OutputBuffer = outputBuffer; _ansiRequestScheduler = ansiRequestScheduler; InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e); InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e); InputProcessor.MouseEvent += (s, e) => { //Logging.Logger.LogTrace ($"Mouse {e.Flags} at x={e.ScreenPosition.X} y={e.ScreenPosition.Y}"); MouseEvent?.Invoke (s, e); }; SizeMonitor = sizeMonitor; sizeMonitor.SizeChanged += (_, e) => { SetScreenSize (e.Size!.Value.Width, e.Size.Value.Height); //SizeChanged?.Invoke (this, e); }; CreateClipboard (); } /// public event EventHandler? SizeChanged; /// public IInputProcessor InputProcessor { get; } /// public IOutputBuffer OutputBuffer { get; } /// public ISizeMonitor SizeMonitor { get; } private void CreateClipboard () { if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake")) { if (Clipboard is null) { Clipboard = new FakeClipboard (); } return; } PlatformID p = Environment.OSVersion.Platform; if (p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows) { Clipboard = new WindowsClipboard (); } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { Clipboard = new MacOSXClipboard (); } else if (PlatformDetection.IsWSLPlatform ()) { Clipboard = new WSLClipboard (); } // Clipboard is set to FakeClipboard at initialization } /// public Rectangle Screen => //if (Application.RunningUnitTests && _output is WindowsConsoleOutput or NetOutput) //{ // // In unit tests, we don't have a real output, so we return an empty rectangle. // return Rectangle.Empty; //} new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows); /// public virtual void SetScreenSize (int width, int height) { OutputBuffer.SetSize (width, height); _output.SetSize (width, height); SizeChanged?.Invoke (this, new (new (width, height))); } /// public Region? Clip { get => OutputBuffer.Clip; set => OutputBuffer.Clip = value; } /// public IClipboard? Clipboard { get; private set; } = new FakeClipboard (); /// public int Col => OutputBuffer.Col; /// public int Cols { get => OutputBuffer.Cols; set => OutputBuffer.Cols = value; } /// public Cell [,]? Contents { get => OutputBuffer.Contents; set => OutputBuffer.Contents = value; } /// public int Left { get => OutputBuffer.Left; set => OutputBuffer.Left = value; } /// public int Row => OutputBuffer.Row; /// public int Rows { get => OutputBuffer.Rows; set => OutputBuffer.Rows = value; } /// public int Top { get => OutputBuffer.Top; set => OutputBuffer.Top = value; } // TODO: Probably not everyone right? /// public bool SupportsTrueColor => true; /// public bool Force16Colors { get => Application.Force16Colors || !SupportsTrueColor; set => Application.Force16Colors = value || !SupportsTrueColor; } /// public Attribute CurrentAttribute { get => OutputBuffer.CurrentAttribute; set => OutputBuffer.CurrentAttribute = value; } /// public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); } /// public void AddRune (char c) { OutputBuffer.AddRune (c); } /// public void AddStr (string str) { OutputBuffer.AddStr (str); } /// Clears the of the driver. public void ClearContents () { OutputBuffer.ClearContents (); ClearedContents?.Invoke (this, new MouseEventArgs ()); } /// public event EventHandler? ClearedContents; /// public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); } /// public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); } /// public virtual string GetVersionInfo () { string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName)); return type; } /// public bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value); /// Tests whether the specified coordinate are valid for drawing the specified Text. /// Used to determine if one or two columns are required. /// The column. /// The row. /// /// if the coordinate is outside the screen bounds or outside of /// . /// otherwise. /// public bool IsValidLocation (string text, int col, int row) { return OutputBuffer.IsValidLocation (text, col, row); } /// public void Move (int col, int row) { OutputBuffer.Move (col, row); } // TODO: Probably part of output /// public bool SetCursorVisibility (CursorVisibility visibility) { _lastCursor = visibility; _output.SetCursorVisibility (visibility); return true; } /// public bool GetCursorVisibility (out CursorVisibility current) { current = _lastCursor; return true; } /// public void Suspend () { // BUGBUG: This is all platform-specific and should not be implemented here. // BUGBUG: This needs to be in each platform's driver implementation. if (Environment.OSVersion.Platform != PlatformID.Unix) { return; } Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); try { Console.ResetColor (); Console.Clear (); //Disable alternative screen buffer. Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); //Set cursor key to cursor. Console.Out.Write (EscSeqUtils.CSI_ShowCursor); Platform.Suspend (); //Enable alternative screen buffer. Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); } catch (Exception ex) { Logging.Error ($"Error suspending terminal: {ex.Message}"); } Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); } /// public void UpdateCursor () { _output.SetCursorPosition (Col, Row); } /// public void Init () { throw new NotSupportedException (); } /// public void End () { // TODO: Nope } /// public Attribute SetAttribute (Attribute newAttribute) { Attribute currentAttribute = OutputBuffer.CurrentAttribute; OutputBuffer.CurrentAttribute = newAttribute; return currentAttribute; } /// public Attribute GetAttribute () => OutputBuffer.CurrentAttribute; /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; /// public event EventHandler? KeyUp; /// Event fired when a mouse event occurs. public event EventHandler? MouseEvent; /// public void WriteRaw (string ansi) { _output.Write (ansi); } /// public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); } /// public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (this, request); } /// public AnsiRequestScheduler GetRequestScheduler () => _ansiRequestScheduler; /// public void Refresh () { // No need we will always draw when dirty } /// public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant (); /// public new string ToString () { StringBuilder sb = new (); Cell [,] contents = Contents!; for (var r = 0; r < Rows; r++) { for (var c = 0; c < Cols; c++) { string text = contents [r, c].Grapheme; sb.Append (text); if (text.GetColumns () > 1) { c++; } } sb.AppendLine (); } return sb.ToString (); } /// public string ToAnsi () { return _output.ToAnsi (OutputBuffer); } }