#nullable enable using System.Runtime.InteropServices; namespace Terminal.Gui.Drivers; internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade { private readonly IConsoleOutput _output; private readonly IOutputBuffer _outputBuffer; private readonly AnsiRequestScheduler _ansiRequestScheduler; private CursorVisibility _lastCursor = CursorVisibility.Default; /// /// The event fired when the screen changes (size, position, etc.). /// public event EventHandler? SizeChanged; public IInputProcessor InputProcessor { get; } public IOutputBuffer OutputBuffer => _outputBuffer; public IConsoleSizeMonitor ConsoleSizeMonitor { get; } public ConsoleDriverFacade ( IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler, IConsoleSizeMonitor 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); }; ConsoleSizeMonitor = sizeMonitor; sizeMonitor.SizeChanged += (_, e) => { SetScreenSize(e.Size!.Value.Width, e.Size.Value.Height); //SizeChanged?.Invoke (this, e); }; CreateClipboard (); } private void CreateClipboard () { if (FakeDriver.FakeBehaviors.UseFakeClipboard) { Clipboard = new FakeClipboard ( FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse); return; } PlatformID p = Environment.OSVersion.Platform; if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { Clipboard = new WindowsClipboard (); } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { Clipboard = new MacOSXClipboard (); } else if (PlatformDetection.IsWSLPlatform ()) { Clipboard = new WSLClipboard (); } else { Clipboard = new FakeClipboard (); } } /// Gets the location and size of the terminal screen. public Rectangle Screen { get { if (ConsoleDriver.RunningUnitTests && _output is WindowsOutput or NetOutput) { // In unit tests, we don't have a real output, so we return an empty rectangle. return Rectangle.Empty; } return new (0, 0, _outputBuffer.Cols, _outputBuffer.Rows); } } /// /// Sets the screen size for testing purposes. Only supported by FakeDriver. /// /// The new width in columns. /// The new height in rows. /// Thrown when called on non-FakeDriver instances. public virtual void SetScreenSize (int width, int height) { _outputBuffer.SetSize (width, height); _output.SetSize (width, height); SizeChanged?.Invoke(this, new (new (width, height))); } /// /// Gets or sets the clip rectangle that and are subject /// to. /// /// The rectangle describing the of region. public Region? Clip { get => _outputBuffer.Clip; set => _outputBuffer.Clip = value; } /// Get the operating system clipboard. public IClipboard Clipboard { get; private set; } = new FakeClipboard (); /// /// Gets the column last set by . and are used by /// and to determine where to add content. /// public int Col => _outputBuffer.Col; /// The number of columns visible in the terminal. public int Cols { get => _outputBuffer.Cols; set => _outputBuffer.Cols = value; } /// /// The contents of the application output. The driver outputs this buffer to the terminal. /// The format of the array is rows, columns. The first index is the row, the second index is the column. /// public Cell [,]? Contents { get => _outputBuffer.Contents; set => _outputBuffer.Contents = value; } /// The leftmost column in the terminal. public int Left { get => _outputBuffer.Left; set => _outputBuffer.Left = value; } /// /// Gets the row last set by . and are used by /// and to determine where to add content. /// public int Row => _outputBuffer.Row; /// The number of rows visible in the terminal. public int Rows { get => _outputBuffer.Rows; set => _outputBuffer.Rows = value; } /// The topmost row in the terminal. public int Top { get => _outputBuffer.Top; set => _outputBuffer.Top = value; } // TODO: Probably not everyone right? /// Gets whether the supports TrueColor output. public bool SupportsTrueColor => true; // TODO: Currently ignored /// /// Gets or sets whether the should use 16 colors instead of the default TrueColors. /// See to change this setting via . /// /// /// /// Will be forced to if is /// , indicating that the cannot support TrueColor. /// /// public bool Force16Colors { get => Application.Force16Colors || !SupportsTrueColor; set => Application.Force16Colors = value || !SupportsTrueColor; } /// /// The that will be used for the next or /// call. /// public Attribute CurrentAttribute { get => _outputBuffer.CurrentAttribute; set => _outputBuffer.CurrentAttribute = value; } /// Adds the specified rune to the display at the current cursor position. /// /// /// When the method returns, will be incremented by the number of columns /// required, even if the new column value is outside of the /// or screen /// dimensions defined by . /// /// /// If requires more than one column, and plus the number /// of columns /// needed exceeds the or screen dimensions, the default Unicode replacement /// character (U+FFFD) /// will be added instead. /// /// /// Rune to add. public void AddRune (Rune rune) { _outputBuffer.AddRune (rune); } /// /// Adds the specified to the display at the current cursor position. This method is a /// convenience method that calls with the /// constructor. /// /// Character to add. public void AddRune (char c) { _outputBuffer.AddRune (c); } /// Adds the to the display at the cursor position. /// /// /// When the method returns, will be incremented by the number of columns /// required, unless the new column value is outside of the /// or screen /// dimensions defined by . /// /// If requires more columns than are available, the output will be clipped. /// /// String. public void AddStr (string str) { _outputBuffer.AddStr (str); } /// Clears the of the driver. public void ClearContents () { _outputBuffer.ClearContents (); ClearedContents?.Invoke (this, new MouseEventArgs ()); } /// /// Raised each time is called. For benchmarking. /// public event EventHandler? ClearedContents; /// /// Fills the specified rectangle with the specified rune, using /// /// /// The value of is honored. Any parts of the rectangle not in the clip will not be /// drawn. /// /// The Screen-relative rectangle. /// The Rune used to fill the rectangle public void FillRect (Rectangle rect, Rune rune = default) { _outputBuffer.FillRect (rect, rune); } /// /// Fills the specified rectangle with the specified . This method is a convenience method /// that calls . /// /// /// 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; } /// Tests if the specified rune is supported by the driver. /// /// /// if the rune can be properly presented; if the driver does not /// support displaying this rune. /// public bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); } /// Tests whether the specified coordinate are valid for drawing the specified Rune. /// 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 (Rune rune, int col, int row) { return _outputBuffer.IsValidLocation (rune, col, row); } /// /// Updates and to the specified column and row in /// . /// Used by and to determine /// where to add content. /// /// /// This does not move the cursor on the screen, it only updates the internal state of the driver. /// /// If or are negative or beyond /// and /// , the method still sets those properties. /// /// /// Column to move to. /// Row to move to. public void Move (int col, int row) { _outputBuffer.Move (col, row); } // TODO: Probably part of output /// Sets the terminal cursor visibility. /// The wished /// upon success 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 () { if (Environment.OSVersion.Platform != PlatformID.Unix) { return; } Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); if (!ConsoleDriver.RunningUnitTests) { 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); Application.LayoutAndDraw (); } Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); } /// /// Sets the position of the terminal cursor to and /// . /// public void UpdateCursor () { _output.SetCursorPosition (Col, Row); } /// Initializes the driver public void Init () { throw new NotSupportedException (); } /// Ends the execution of the console driver. public void End () { // TODO: Nope } /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. /// Implementations should call base.SetAttribute(c). /// C. /// The previously set Attribute. public Attribute SetAttribute (Attribute newAttribute) { Attribute currentAttribute = _outputBuffer.CurrentAttribute; _outputBuffer.CurrentAttribute = newAttribute; return currentAttribute; } /// Gets the current . /// The current attribute. public Attribute GetAttribute () { return _outputBuffer.CurrentAttribute; } /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; /// Event fired when a key is released. /// /// Drivers that do not support key release events will fire this event after /// processing is /// complete. /// public event EventHandler? KeyUp; /// Event fired when a mouse event occurs. public event EventHandler? MouseEvent; /// /// Provide proper writing to send escape sequence recognized by the . /// /// public void WriteRaw (string ansi) { _output.Write (ansi); } /// /// Queues the given for execution /// /// public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (request); } public AnsiRequestScheduler GetRequestScheduler () { return _ansiRequestScheduler; } /// public void Refresh () { // No need we will always draw when dirty } }