From 9de21e066d0b4566785f3ca70233a1aee8a9eae0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 30 Sep 2024 01:37:13 +0100 Subject: [PATCH 001/151] Fixes #3767. Allowing any driver to request ANSI escape sequence with immediate response. --- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 1eb63e34a..17589d101 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -304,6 +304,91 @@ public static class EscSeqUtils } #nullable enable + /// + /// Execute an ANSI escape sequence escape which may return a response or error. + /// + /// The ANSI escape sequence to request. + /// A tuple with the response and error. + public static (string response, string error) ExecuteAnsiRequest (string ansiRequest) + { + var response = new StringBuilder (); + var error = new StringBuilder (); + var foundEscapeSequence = false; + + try + { + switch (Application.Driver) + { + case NetDriver netDriver: + netDriver.StopReportingMouseMoves (); + + break; + case CursesDriver cursesDriver: + cursesDriver.StopReportingMouseMoves (); + + break; + } + + Thread.Sleep (100); // Allow time for mouse stopping + + // Flush the input buffer to avoid reading stale input + while (Console.KeyAvailable) + { + Console.ReadKey (true); + } + + // Send the ANSI escape sequence + Console.Write (ansiRequest); + Console.Out.Flush (); // Ensure the request is sent + + // Read the response from stdin (response should come back as input) + Thread.Sleep (100); // Allow time for the terminal to respond + + // Read input until no more characters are available or another \u001B is encountered + while (Console.KeyAvailable) + { + // Peek the next key + ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console + + if (keyInfo.KeyChar == '\u001B') // Check if the key is Escape (ANSI escape sequence starts) + { + if (foundEscapeSequence) + { + // If we already found one \u001B, break out of the loop when another is found + break; + } + else + { + foundEscapeSequence = true; // Mark that we've encountered the first escape sequence + } + } + + // Append the current key to the response + response.Append (keyInfo.KeyChar); + } + } + catch (Exception ex) + { + error.AppendLine ($"Error executing ANSI request: {ex.Message}"); + } + finally + { + switch (Application.Driver) + { + case NetDriver netDriver: + netDriver.StartReportingMouseMoves (); + + break; + case CursesDriver cursesDriver: + cursesDriver.StartReportingMouseMoves (); + + break; + } + } + + return (response.ToString (), error.ToString ()); + } + /// /// Gets the c1Control used in the called escape sequence. /// From 0b3d2199611fcdc90300a91fa8028bbae2e491c4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 30 Sep 2024 01:41:46 +0100 Subject: [PATCH 002/151] Using a more appropriate request for cursor position. --- Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 17589d101..8348d031a 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -1399,10 +1399,10 @@ public static class EscSeqUtils #region Requests /// - /// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR) - /// https://terminalguide.namepad.de/seq/csi_sn__p-6/ + /// ESC [ 6 n - Request Cursor Position Report (CPR) + /// https://terminalguide.namepad.de/seq/csi_sn-6/ /// - public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n"; + public static readonly string CSI_RequestCursorPositionReport = CSI + "6n"; /// /// The terminal reply to . ESC [ ? (y) ; (x) R From c89a9c8dfb98b3685b22c11fc3416c8e43092995 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 30 Sep 2024 18:53:02 +0100 Subject: [PATCH 003/151] Add AnsiEscapeSequenceRequest and AnsiEscapeSequenceResponse classes. --- .../AnsiEscapeSequenceRequest.cs | 163 ++++++++++++++++++ .../AnsiEscapeSequenceResponse.cs | 53 ++++++ .../CursesDriver/CursesDriver.cs | 6 + .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 119 +------------ Terminal.Gui/ConsoleDrivers/NetDriver.cs | 86 ++++----- 5 files changed, 275 insertions(+), 152 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs new file mode 100644 index 000000000..30546501b --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -0,0 +1,163 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// Describes an ongoing ANSI request sent to the console. +/// Use to handle the response +/// when console answers the request. +/// +public class AnsiEscapeSequenceRequest +{ + /// + /// Execute an ANSI escape sequence escape which may return a response or error. + /// + /// The ANSI escape sequence to request. + /// A with the response, error, terminator and value. + public static AnsiEscapeSequenceResponse ExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + { + var response = new StringBuilder (); + var error = new StringBuilder (); + var savedIsReportingMouseMoves = false; + + try + { + switch (Application.Driver) + { + case NetDriver netDriver: + savedIsReportingMouseMoves = netDriver.IsReportingMouseMoves; + + if (savedIsReportingMouseMoves) + { + netDriver.StopReportingMouseMoves (); + } + + break; + case CursesDriver cursesDriver: + savedIsReportingMouseMoves = cursesDriver.IsReportingMouseMoves; + + if (savedIsReportingMouseMoves) + { + cursesDriver.StopReportingMouseMoves (); + } + + break; + } + + Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer + + // Flush the input buffer to avoid reading stale input + while (Console.KeyAvailable) + { + Console.ReadKey (true); + } + + // Send the ANSI escape sequence + Console.Write (ansiRequest.Request); + Console.Out.Flush (); // Ensure the request is sent + + // Read the response from stdin (response should come back as input) + Thread.Sleep (100); // Allow time for the terminal to respond + + // Read input until no more characters are available or the terminator is encountered + while (Console.KeyAvailable) + { + // Peek the next key + ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console + + // Append the current key to the response + response.Append (keyInfo.KeyChar); + + if (keyInfo.KeyChar == ansiRequest.Terminator [^1]) // Check if the key is terminator (ANSI escape sequence ends) + { + // Break out of the loop when terminator is found + break; + } + } + + if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1])) + { + throw new InvalidOperationException ($"Terminator doesn't ends with: {ansiRequest.Terminator [^1]}"); + } + } + catch (Exception ex) + { + error.AppendLine ($"Error executing ANSI request: {ex.Message}"); + } + finally + { + if (savedIsReportingMouseMoves) + { + switch (Application.Driver) + { + case NetDriver netDriver: + netDriver.StartReportingMouseMoves (); + + break; + case CursesDriver cursesDriver: + cursesDriver.StartReportingMouseMoves (); + + break; + } + } + } + + var values = new string? [] { null }; + + if (string.IsNullOrEmpty (error.ToString ())) + { + (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ()); + } + + AnsiEscapeSequenceResponse ansiResponse = new () + { Response = response.ToString (), Error = error.ToString (), Terminator = response.ToString () [^1].ToString (), Value = values [0] }; + + // Invoke the event if it's subscribed + ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse); + + return ansiResponse; + } + + /// + /// Request to send e.g. see + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// + public required string Request { get; init; } + + /// + /// Invoked when the console responds with an ANSI response code that matches the + /// + /// + public event EventHandler? ResponseReceived; + + /// + /// + /// The terminator that uniquely identifies the type of response as responded + /// by the console. e.g. for + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// the terminator is + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator + /// + /// . + /// + /// + /// After sending a request, the first response with matching terminator will be matched + /// to the oldest outstanding request. + /// + /// + public required string Terminator { get; init; } + + /// + /// The value expected in the response e.g. + /// + /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value + /// + /// which will have a 't' as terminator but also other different request may return the same terminator with a + /// different value. + /// + public string? Value { get; init; } +} diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs new file mode 100644 index 000000000..df9851155 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs @@ -0,0 +1,53 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// Describes a finished ANSI received from the console. +/// +public class AnsiEscapeSequenceResponse +{ + /// + /// Error received from e.g. see + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// + public required string Error { get; init; } + + /// + /// Response received from e.g. see + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// . + /// + public required string Response { get; init; } + + /// + /// + /// The terminator that uniquely identifies the type of response as responded + /// by the console. e.g. for + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// the terminator is + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator + /// + /// + /// + /// The received terminator must match to the terminator sent by the request. + /// + /// + public required string Terminator { get; init; } + + /// + /// The value expected in the response e.g. + /// + /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value + /// + /// which will have a 't' as terminator but also other different request may return the same terminator with a + /// different value. + /// + public string? Value { get; init; } +} diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index b806457d4..ff4f95bdb 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -177,11 +177,15 @@ internal class CursesDriver : ConsoleDriver return true; } + public bool IsReportingMouseMoves { get; private set; } + public void StartReportingMouseMoves () { if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + + IsReportingMouseMoves = true; } } @@ -190,6 +194,8 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + + IsReportingMouseMoves = false; } } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 8348d031a..d32a8e01f 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -304,91 +304,6 @@ public static class EscSeqUtils } #nullable enable - /// - /// Execute an ANSI escape sequence escape which may return a response or error. - /// - /// The ANSI escape sequence to request. - /// A tuple with the response and error. - public static (string response, string error) ExecuteAnsiRequest (string ansiRequest) - { - var response = new StringBuilder (); - var error = new StringBuilder (); - var foundEscapeSequence = false; - - try - { - switch (Application.Driver) - { - case NetDriver netDriver: - netDriver.StopReportingMouseMoves (); - - break; - case CursesDriver cursesDriver: - cursesDriver.StopReportingMouseMoves (); - - break; - } - - Thread.Sleep (100); // Allow time for mouse stopping - - // Flush the input buffer to avoid reading stale input - while (Console.KeyAvailable) - { - Console.ReadKey (true); - } - - // Send the ANSI escape sequence - Console.Write (ansiRequest); - Console.Out.Flush (); // Ensure the request is sent - - // Read the response from stdin (response should come back as input) - Thread.Sleep (100); // Allow time for the terminal to respond - - // Read input until no more characters are available or another \u001B is encountered - while (Console.KeyAvailable) - { - // Peek the next key - ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console - - if (keyInfo.KeyChar == '\u001B') // Check if the key is Escape (ANSI escape sequence starts) - { - if (foundEscapeSequence) - { - // If we already found one \u001B, break out of the loop when another is found - break; - } - else - { - foundEscapeSequence = true; // Mark that we've encountered the first escape sequence - } - } - - // Append the current key to the response - response.Append (keyInfo.KeyChar); - } - } - catch (Exception ex) - { - error.AppendLine ($"Error executing ANSI request: {ex.Message}"); - } - finally - { - switch (Application.Driver) - { - case NetDriver netDriver: - netDriver.StartReportingMouseMoves (); - - break; - case CursesDriver cursesDriver: - cursesDriver.StartReportingMouseMoves (); - - break; - } - } - - return (response.ToString (), error.ToString ()); - } - /// /// Gets the c1Control used in the called escape sequence. /// @@ -1399,15 +1314,11 @@ public static class EscSeqUtils #region Requests /// - /// ESC [ 6 n - Request Cursor Position Report (CPR) - /// https://terminalguide.namepad.de/seq/csi_sn-6/ + /// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR) + /// https://terminalguide.namepad.de/seq/csi_sn__p-6/ + /// The terminal reply to . ESC [ ? (y) ; (x) ; 1 R /// - public static readonly string CSI_RequestCursorPositionReport = CSI + "6n"; - - /// - /// The terminal reply to . ESC [ ? (y) ; (x) R - /// - public const string CSI_RequestCursorPositionReport_Terminator = "R"; + public static readonly AnsiEscapeSequenceRequest CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" }; /// /// ESC [ 0 c - Send Device Attributes (Primary DA) @@ -1426,37 +1337,25 @@ public static class EscSeqUtils /// 28 = Rectangular area operations /// 32 = Text macros /// 42 = ISO Latin-2 character set + /// The terminator indicating a reply to or + /// /// - public static readonly string CSI_SendDeviceAttributes = CSI + "0c"; + public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" }; /// /// ESC [ > 0 c - Send Device Attributes (Secondary DA) /// Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220) - /// - public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c"; - - /// /// The terminator indicating a reply to or /// /// - public const string CSI_ReportDeviceAttributes_Terminator = "c"; + public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" }; /// /// CSI 1 8 t | yes | yes | yes | report window size in chars /// https://terminalguide.namepad.de/seq/csi_st-18/ - /// - public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t"; - - /// /// The terminator indicating a reply to : ESC [ 8 ; height ; width t /// - public const string CSI_ReportTerminalSizeInChars_Terminator = "t"; - - /// - /// The value of the response to indicating value 1 and 2 are the terminal - /// size in chars. - /// - public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8"; + public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" }; #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 8aaddc321..79ddbcde2 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -591,52 +591,48 @@ internal class NetEvents : IDisposable private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) { - switch (terminating) - { + if (terminating == + // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. - case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator: - var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; + // The observation is correct because the response isn't immediate and this is useless + EscSeqUtils.CSI_RequestCursorPositionReport.Terminator) + { + var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; - if (_lastCursorPosition.Y != point.Y) - { - _lastCursorPosition = point; - var eventType = EventType.WindowPosition; - var winPositionEv = new WindowPositionEvent { CursorPosition = point }; + if (_lastCursorPosition.Y != point.Y) + { + _lastCursorPosition = point; + var eventType = EventType.WindowPosition; + var winPositionEv = new WindowPositionEvent { CursorPosition = point }; - _inputQueue.Enqueue ( - new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } - ); - } - else - { - return; - } - - break; - - case EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator: - switch (values [0]) - { - case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue: - EnqueueWindowSizeEvent ( - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0), - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0) - ); - - break; - default: - EnqueueRequestResponseEvent (c1Control, code, values, terminating); - - break; - } - - break; - default: + _inputQueue.Enqueue ( + new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } + ); + } + else + { + return; + } + } + else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator) + { + if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value) + { + EnqueueWindowSizeEvent ( + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0), + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0) + ); + } + else + { EnqueueRequestResponseEvent (c1Control, code, values, terminating); - - break; + } + } + else + { + EnqueueRequestResponseEvent (c1Control, code, values, terminating); } _inputReady.Set (); @@ -1377,11 +1373,15 @@ internal class NetDriver : ConsoleDriver #region Mouse Handling + public bool IsReportingMouseMoves { get; private set; } + public void StartReportingMouseMoves () { if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + + IsReportingMouseMoves = true; } } @@ -1390,6 +1390,8 @@ internal class NetDriver : ConsoleDriver if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + + IsReportingMouseMoves = false; } } From 21c31557c0cd175ccf5a7bdc4c65ef959f7af123 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 1 Oct 2024 17:31:08 +0100 Subject: [PATCH 004/151] Prevents empty response error. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 30546501b..ca11e8a91 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -76,7 +76,7 @@ public class AnsiEscapeSequenceRequest if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1])) { - throw new InvalidOperationException ($"Terminator doesn't ends with: {ansiRequest.Terminator [^1]}"); + throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'"); } } catch (Exception ex) @@ -109,7 +109,10 @@ public class AnsiEscapeSequenceRequest } AnsiEscapeSequenceResponse ansiResponse = new () - { Response = response.ToString (), Error = error.ToString (), Terminator = response.ToString () [^1].ToString (), Value = values [0] }; + { + Response = response.ToString (), Error = error.ToString (), + Terminator = string.IsNullOrEmpty (response.ToString ()) ? "" : response.ToString () [^1].ToString (), Value = values [0] + }; // Invoke the event if it's subscribed ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse); From 7142a0489f3f1eded35ede0664c7cb0b4a23cf25 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 1 Oct 2024 17:34:41 +0100 Subject: [PATCH 005/151] Add AnsiEscapeSequenceRequests scenario. --- .../Scenarios/AnsiEscapeSequenceRequest.cs | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs new file mode 100644 index 000000000..7882c4449 --- /dev/null +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")] +[ScenarioCategory ("Controls")] +public sealed class AnsiEscapeSequenceRequests : Scenario +{ + public override void Main () + { + // Init + Application.Init (); + + // Setup - Create a top-level application window and configure it. + Window appWindow = new () + { + Title = GetQuitKeyAndName (), + }; + appWindow.Padding.Thickness = new (1); + + var scrRequests = new List + { + "CSI_SendDeviceAttributes", + "CSI_ReportTerminalSizeInChars", + "CSI_RequestCursorPositionReport", + "CSI_SendDeviceAttributes2" + }; + + var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) }; + appWindow.Add (cbRequests); + + var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:"}; + var tfRequest = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20 }; + appWindow.Add (label, tfRequest); + + label = new Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest), Text = "Value:" }; + var tfValue = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 6 }; + appWindow.Add (label, tfValue); + + label = new Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue), Text = "Terminator:" }; + var tfTerminator = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 4 }; + appWindow.Add (label, tfTerminator); + + cbRequests.SelectedItemChanged += (s, e) => + { + var selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem]; + AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null; + switch (selAnsiEscapeSequenceRequestName) + { + case "CSI_SendDeviceAttributes": + selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes; + break; + case "CSI_ReportTerminalSizeInChars": + selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportTerminalSizeInChars; + break; + case "CSI_RequestCursorPositionReport": + selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_RequestCursorPositionReport; + break; + case "CSI_SendDeviceAttributes2": + selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes2; + break; + } + + tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : ""; + tfValue.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Value ?? "" : ""; + tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : ""; + }; + // Forces raise cbRequests.SelectedItemChanged to update TextFields + cbRequests.SelectedItem = 0; + + label = new Label { Y = Pos.Bottom (tfRequest) + 2, Text = "Response:" }; + var tvResponse = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 30, Height = 4, ReadOnly = true }; + appWindow.Add (label, tvResponse); + + label = new Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse), Text = "Error:" }; + var tvError = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Height = 4, ReadOnly = true }; + appWindow.Add (label, tvError); + + label = new Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError), Text = "Value:" }; + var tvValue = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 6, Height = 4, ReadOnly = true }; + appWindow.Add (label, tvValue); + + label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue), Text = "Terminator:" }; + var tvTerminator = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 4, Height = 4, ReadOnly = true }; + appWindow.Add (label, tvTerminator); + + var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request" }; + btnResponse.Accept += (s, e) => + { + var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest + { + Request = tfRequest.Text, + Terminator = tfTerminator.Text, + Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text + }; + + var ansiEscapeSequenceResponse = AnsiEscapeSequenceRequest.ExecuteAnsiRequest ( + ansiEscapeSequenceRequest + ); + + tvResponse.Text =ansiEscapeSequenceResponse.Response; + tvError.Text = ansiEscapeSequenceResponse.Error; + tvValue.Text = ansiEscapeSequenceResponse.Value ?? ""; + tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; + }; + appWindow.Add (btnResponse); + + appWindow.Add (new Label { Y = Pos.Bottom (btnResponse) + 2, Text = "You can send other requests by editing the TextFields." }); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); + } +} From dd3179697debed40257ee4ac39dc7a4f88ae61e2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 1 Oct 2024 22:13:32 +0100 Subject: [PATCH 006/151] Improving scenario layout. --- .../Scenarios/AnsiEscapeSequenceRequest.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs index 7882c4449..e8b5a13e5 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs @@ -30,16 +30,16 @@ public sealed class AnsiEscapeSequenceRequests : Scenario var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) }; appWindow.Add (cbRequests); - var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:"}; - var tfRequest = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20 }; + var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:" }; + var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 }; appWindow.Add (label, tfRequest); - label = new Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest), Text = "Value:" }; - var tfValue = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 6 }; + label = new Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "Value:" }; + var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 }; appWindow.Add (label, tfValue); - label = new Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue), Text = "Terminator:" }; - var tfTerminator = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 4 }; + label = new Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "Terminator:" }; + var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 }; appWindow.Add (label, tfTerminator); cbRequests.SelectedItemChanged += (s, e) => @@ -70,19 +70,19 @@ public sealed class AnsiEscapeSequenceRequests : Scenario cbRequests.SelectedItem = 0; label = new Label { Y = Pos.Bottom (tfRequest) + 2, Text = "Response:" }; - var tvResponse = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 30, Height = 4, ReadOnly = true }; + var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; appWindow.Add (label, tvResponse); - label = new Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse), Text = "Error:" }; - var tvError = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Height = 4, ReadOnly = true }; + label = new Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "Error:" }; + var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; appWindow.Add (label, tvError); - label = new Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError), Text = "Value:" }; - var tvValue = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 6, Height = 4, ReadOnly = true }; + label = new Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "Value:" }; + var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true }; appWindow.Add (label, tvValue); - label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue), Text = "Terminator:" }; - var tvTerminator = new TextView { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 4, Height = 4, ReadOnly = true }; + label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "Terminator:" }; + var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true }; appWindow.Add (label, tvTerminator); var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request" }; @@ -99,7 +99,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario ansiEscapeSequenceRequest ); - tvResponse.Text =ansiEscapeSequenceResponse.Response; + tvResponse.Text = ansiEscapeSequenceResponse.Response; tvError.Text = ansiEscapeSequenceResponse.Error; tvValue.Text = ansiEscapeSequenceResponse.Value ?? ""; tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; From 8d52fe731fc33fb3c102055518454e6fb7f91f6e Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 2 Oct 2024 01:21:36 +0100 Subject: [PATCH 007/151] Fix NetDriver read key issue. --- .../AnsiEscapeSequenceRequest.cs | 38 ++++++++++++++----- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 13 ++++--- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index ca11e8a91..d6a5d3470 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -18,19 +18,31 @@ public class AnsiEscapeSequenceRequest var response = new StringBuilder (); var error = new StringBuilder (); var savedIsReportingMouseMoves = false; + NetDriver? netDriver = null; try { switch (Application.Driver) { - case NetDriver netDriver: - savedIsReportingMouseMoves = netDriver.IsReportingMouseMoves; + case NetDriver: + netDriver = Application.Driver as NetDriver; + savedIsReportingMouseMoves = netDriver!.IsReportingMouseMoves; if (savedIsReportingMouseMoves) { netDriver.StopReportingMouseMoves (); } + while (Console.KeyAvailable) + { + netDriver._mainLoopDriver._netEvents._waitForStart.Set (); + netDriver._mainLoopDriver._netEvents._waitForStart.Reset (); + + netDriver._mainLoopDriver._netEvents._forceRead = true; + } + + netDriver._mainLoopDriver._netEvents._forceRead = false; + break; case CursesDriver cursesDriver: savedIsReportingMouseMoves = cursesDriver.IsReportingMouseMoves; @@ -43,12 +55,19 @@ public class AnsiEscapeSequenceRequest break; } - Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer - - // Flush the input buffer to avoid reading stale input - while (Console.KeyAvailable) + if (netDriver is { }) { - Console.ReadKey (true); + NetEvents._suspendRead = true; + } + else + { + Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer + + // Flush the input buffer to avoid reading stale input + while (Console.KeyAvailable) + { + Console.ReadKey (true); + } } // Send the ANSI escape sequence @@ -89,8 +108,9 @@ public class AnsiEscapeSequenceRequest { switch (Application.Driver) { - case NetDriver netDriver: - netDriver.StartReportingMouseMoves (); + case NetDriver: + NetEvents._suspendRead = false; + netDriver!.StartReportingMouseMoves (); break; case CursesDriver cursesDriver: diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 79ddbcde2..6f5204fae 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -136,7 +136,7 @@ internal class NetEvents : IDisposable { private readonly ManualResetEventSlim _inputReady = new (false); private CancellationTokenSource _inputReadyCancellationTokenSource; - private readonly ManualResetEventSlim _waitForStart = new (false); + internal readonly ManualResetEventSlim _waitForStart = new (false); //CancellationTokenSource _waitForStartCancellationTokenSource; private readonly ManualResetEventSlim _winChange = new (false); @@ -202,7 +202,7 @@ internal class NetEvents : IDisposable { // if there is a key available, return it without waiting // (or dispatching work to the thread queue) - if (Console.KeyAvailable) + if (Console.KeyAvailable && !_suspendRead) { return Console.ReadKey (intercept); } @@ -211,7 +211,7 @@ internal class NetEvents : IDisposable { Task.Delay (100, cancellationToken).Wait (cancellationToken); - if (Console.KeyAvailable) + if (Console.KeyAvailable && !_suspendRead) { return Console.ReadKey (intercept); } @@ -222,6 +222,9 @@ internal class NetEvents : IDisposable return default (ConsoleKeyInfo); } + internal bool _forceRead; + internal static bool _suspendRead; + private void ProcessInputQueue () { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) @@ -237,7 +240,7 @@ internal class NetEvents : IDisposable _waitForStart.Reset (); - if (_inputQueue.Count == 0) + if (_inputQueue.Count == 0 || _forceRead) { ConsoleKey key = 0; ConsoleModifiers mod = 0; @@ -812,7 +815,7 @@ internal class NetDriver : ConsoleDriver private const int COLOR_RED = 31; private const int COLOR_WHITE = 37; private const int COLOR_YELLOW = 33; - private NetMainLoop _mainLoopDriver; + internal NetMainLoop _mainLoopDriver; public bool IsWinPlatform { get; private set; } public NetWinVTConsole NetWinConsole { get; private set; } From 1d20bcec7227bf1928e083d59818d223a8622eb0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 2 Oct 2024 12:36:06 +0100 Subject: [PATCH 008/151] Change file name. --- ...siEscapeSequenceRequest.cs => AnsiEscapeSequenceRequests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename UICatalog/Scenarios/{AnsiEscapeSequenceRequest.cs => AnsiEscapeSequenceRequests.cs} (99%) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs similarity index 99% rename from UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs rename to UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index e8b5a13e5..10f152c17 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequest.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -4,7 +4,7 @@ using Terminal.Gui; namespace UICatalog.Scenarios; [ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")] -[ScenarioCategory ("Controls")] +[ScenarioCategory ("Ansi Escape Sequence")] public sealed class AnsiEscapeSequenceRequests : Scenario { public override void Main () From c320fc229de25531d5e9aeca85b5014ebada286f Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 2 Oct 2024 17:15:45 +0100 Subject: [PATCH 009/151] Improves null dequeues handling. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 6f5204fae..7d9043a32 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1754,7 +1754,13 @@ internal class NetMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { - ProcessInput?.Invoke (_resultQueue.Dequeue ().Value); + // Always dequeue even if it's null and invoke if isn't null + InputResult? dequeueResult = _resultQueue.Dequeue (); + + if (dequeueResult is { }) + { + ProcessInput?.Invoke (dequeueResult.Value); + } } } @@ -1810,10 +1816,16 @@ internal class NetMainLoop : IMainLoopDriver _resultQueue.Enqueue (_netEvents.DequeueInput ()); } - while (_resultQueue.Count > 0 && _resultQueue.Peek () is null) + try { - _resultQueue.Dequeue (); + while (_resultQueue.Count > 0 && _resultQueue.Peek () is null) + { + // Dequeue null values + _resultQueue.Dequeue (); + } } + catch (InvalidOperationException) // Peek can raise an exception + { } if (_resultQueue.Count > 0) { From 8050202db89a81792e479b778650ab2be583ed15 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 5 Oct 2024 21:31:02 +0100 Subject: [PATCH 010/151] Replace ExecuteAnsiRequest with TryParse. --- .../AnsiEscapeSequenceRequest.cs | 7 ++++-- .../Scenarios/AnsiEscapeSequenceRequests.cs | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index d6a5d3470..10ffd0953 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -12,8 +12,9 @@ public class AnsiEscapeSequenceRequest /// Execute an ANSI escape sequence escape which may return a response or error. /// /// The ANSI escape sequence to request. + /// When this method returns , an object containing the response with an empty error. /// A with the response, error, terminator and value. - public static AnsiEscapeSequenceResponse ExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + public static bool TryParse (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) { var response = new StringBuilder (); var error = new StringBuilder (); @@ -137,7 +138,9 @@ public class AnsiEscapeSequenceRequest // Invoke the event if it's subscribed ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse); - return ansiResponse; + result = ansiResponse; + + return string.IsNullOrWhiteSpace (result.Error) && !string.IsNullOrWhiteSpace (result.Response); } /// diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 10f152c17..4f0ffdf48 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -86,6 +86,10 @@ public sealed class AnsiEscapeSequenceRequests : Scenario appWindow.Add (label, tvTerminator); var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request" }; + + var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 }; + appWindow.Add (lblSuccess); + btnResponse.Accept += (s, e) => { var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest @@ -95,18 +99,30 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text }; - var ansiEscapeSequenceResponse = AnsiEscapeSequenceRequest.ExecuteAnsiRequest ( - ansiEscapeSequenceRequest + var success = AnsiEscapeSequenceRequest.TryParse ( + ansiEscapeSequenceRequest, + out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse ); tvResponse.Text = ansiEscapeSequenceResponse.Response; tvError.Text = ansiEscapeSequenceResponse.Error; tvValue.Text = ansiEscapeSequenceResponse.Value ?? ""; tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; + + if (success) + { + lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"]; + lblSuccess.Text = "Successful"; + } + else + { + lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"]; + lblSuccess.Text = "Error"; + } }; appWindow.Add (btnResponse); - appWindow.Add (new Label { Y = Pos.Bottom (btnResponse) + 2, Text = "You can send other requests by editing the TextFields." }); + appWindow.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "You can send other requests by editing the TextFields." }); // Run - Start the application. Application.Run (appWindow); From bdc6fe6873062eaa95a40f987eb95e4138b69684 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 5 Oct 2024 21:51:20 +0100 Subject: [PATCH 011/151] Code cleanup. --- .../AnsiEscapeSequenceRequest.cs | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 10ffd0953..0db1af58c 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -8,11 +8,48 @@ namespace Terminal.Gui; /// public class AnsiEscapeSequenceRequest { + /// + /// Request to send e.g. see + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// + public required string Request { get; init; } + + /// + /// Invoked when the console responds with an ANSI response code that matches the + /// + /// + public event EventHandler? ResponseReceived; + + /// + /// + /// The terminator that uniquely identifies the type of response as responded + /// by the console. e.g. for + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Request + /// + /// the terminator is + /// + /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator + /// + /// . + /// + /// + /// After sending a request, the first response with matching terminator will be matched + /// to the oldest outstanding request. + /// + /// + public required string Terminator { get; init; } + /// /// Execute an ANSI escape sequence escape which may return a response or error. /// /// The ANSI escape sequence to request. - /// When this method returns , an object containing the response with an empty error. + /// + /// When this method returns , an object containing the response with an empty + /// error. + /// /// A with the response, error, terminator and value. public static bool TryParse (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) { @@ -143,40 +180,6 @@ public class AnsiEscapeSequenceRequest return string.IsNullOrWhiteSpace (result.Error) && !string.IsNullOrWhiteSpace (result.Response); } - /// - /// Request to send e.g. see - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// - public required string Request { get; init; } - - /// - /// Invoked when the console responds with an ANSI response code that matches the - /// - /// - public event EventHandler? ResponseReceived; - - /// - /// - /// The terminator that uniquely identifies the type of response as responded - /// by the console. e.g. for - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// the terminator is - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator - /// - /// . - /// - /// - /// After sending a request, the first response with matching terminator will be matched - /// to the oldest outstanding request. - /// - /// - public required string Terminator { get; init; } - /// /// The value expected in the response e.g. /// From 922f586904d1388cae6ed164e9d29a490bd9fb45 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 5 Oct 2024 22:31:38 +0100 Subject: [PATCH 012/151] Replace from TryParse to TryExecuteAnsiRequest. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 2 +- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 0db1af58c..383eea28b 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -51,7 +51,7 @@ public class AnsiEscapeSequenceRequest /// error. /// /// A with the response, error, terminator and value. - public static bool TryParse (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) + public static bool TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) { var response = new StringBuilder (); var error = new StringBuilder (); diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 4f0ffdf48..88bdada34 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -99,7 +99,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text }; - var success = AnsiEscapeSequenceRequest.TryParse ( + var success = AnsiEscapeSequenceRequest.TryExecuteAnsiRequest ( ansiEscapeSequenceRequest, out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse ); From 2c76ed20ac947ed2c6cd39783c6b7491bcc6da8e Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 7 Oct 2024 12:52:29 +0100 Subject: [PATCH 013/151] Fix exception throwing if no terminator is specified. --- .../AnsiEscapeSequenceRequest.cs | 23 +++++++++++-------- .../Scenarios/AnsiEscapeSequenceRequests.cs | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 383eea28b..d8dc22ad3 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -57,6 +57,7 @@ public class AnsiEscapeSequenceRequest var error = new StringBuilder (); var savedIsReportingMouseMoves = false; NetDriver? netDriver = null; + var values = new string? [] { null }; try { @@ -124,14 +125,20 @@ public class AnsiEscapeSequenceRequest // Append the current key to the response response.Append (keyInfo.KeyChar); - if (keyInfo.KeyChar == ansiRequest.Terminator [^1]) // Check if the key is terminator (ANSI escape sequence ends) + // Read until no key is available if no terminator was specified or + // check if the key is terminator (ANSI escape sequence ends) + if (!string.IsNullOrEmpty (ansiRequest.Terminator) && keyInfo.KeyChar == ansiRequest.Terminator [^1]) { // Break out of the loop when terminator is found break; } } - if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1])) + if (string.IsNullOrEmpty (ansiRequest.Terminator)) + { + error.AppendLine ("Terminator request is empty."); + } + else if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1])) { throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'"); } @@ -142,6 +149,11 @@ public class AnsiEscapeSequenceRequest } finally { + if (string.IsNullOrEmpty (error.ToString ())) + { + (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ()); + } + if (savedIsReportingMouseMoves) { switch (Application.Driver) @@ -159,13 +171,6 @@ public class AnsiEscapeSequenceRequest } } - var values = new string? [] { null }; - - if (string.IsNullOrEmpty (error.ToString ())) - { - (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ()); - } - AnsiEscapeSequenceResponse ansiResponse = new () { Response = response.ToString (), Error = error.ToString (), diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 88bdada34..eae5085bb 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -85,7 +85,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true }; appWindow.Add (label, tvTerminator); - var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request" }; + var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request", IsDefault = true }; var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 }; appWindow.Add (lblSuccess); From 331a49e72b1212611629ba611b0d7fb9fc9fd5f1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 9 Oct 2024 12:54:57 +0100 Subject: [PATCH 014/151] Fix merge errors. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index eae5085bb..8ae89e346 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -90,7 +90,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 }; appWindow.Add (lblSuccess); - btnResponse.Accept += (s, e) => + btnResponse.Accepting += (s, e) => { var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest { From e5c30eb442b549dacaba640661f3fd86db213e96 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 13 Oct 2024 13:00:59 +0100 Subject: [PATCH 015/151] Make AnsiEscapeSequenceRequest agnostic of each driver. --- .../AnsiEscapeSequenceRequest.cs | 67 +++---------------- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 20 ++++++ .../CursesDriver/CursesDriver.cs | 14 +++- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 22 ++++++ Terminal.Gui/ConsoleDrivers/NetDriver.cs | 27 +++++++- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 21 ++++++ 6 files changed, 108 insertions(+), 63 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index d8dc22ad3..1a3a40fd2 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -56,58 +56,21 @@ public class AnsiEscapeSequenceRequest var response = new StringBuilder (); var error = new StringBuilder (); var savedIsReportingMouseMoves = false; - NetDriver? netDriver = null; + ConsoleDriver? driver = null; var values = new string? [] { null }; try { - switch (Application.Driver) + driver = Application.Driver; + + savedIsReportingMouseMoves = driver!.IsReportingMouseMoves; + + if (savedIsReportingMouseMoves) { - case NetDriver: - netDriver = Application.Driver as NetDriver; - savedIsReportingMouseMoves = netDriver!.IsReportingMouseMoves; - - if (savedIsReportingMouseMoves) - { - netDriver.StopReportingMouseMoves (); - } - - while (Console.KeyAvailable) - { - netDriver._mainLoopDriver._netEvents._waitForStart.Set (); - netDriver._mainLoopDriver._netEvents._waitForStart.Reset (); - - netDriver._mainLoopDriver._netEvents._forceRead = true; - } - - netDriver._mainLoopDriver._netEvents._forceRead = false; - - break; - case CursesDriver cursesDriver: - savedIsReportingMouseMoves = cursesDriver.IsReportingMouseMoves; - - if (savedIsReportingMouseMoves) - { - cursesDriver.StopReportingMouseMoves (); - } - - break; + driver.StopReportingMouseMoves (); } - if (netDriver is { }) - { - NetEvents._suspendRead = true; - } - else - { - Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer - - // Flush the input buffer to avoid reading stale input - while (Console.KeyAvailable) - { - Console.ReadKey (true); - } - } + driver!.IsSuspendRead = true; // Send the ANSI escape sequence Console.Write (ansiRequest.Request); @@ -156,18 +119,8 @@ public class AnsiEscapeSequenceRequest if (savedIsReportingMouseMoves) { - switch (Application.Driver) - { - case NetDriver: - NetEvents._suspendRead = false; - netDriver!.StartReportingMouseMoves (); - - break; - case CursesDriver cursesDriver: - cursesDriver.StartReportingMouseMoves (); - - break; - } + driver!.IsSuspendRead = false; + driver.StartReportingMouseMoves (); } } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 7d6de3834..e0b846560 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -562,6 +562,16 @@ public abstract class ConsoleDriver #region Mouse and Keyboard + /// + /// Gets whether the mouse is reporting move events. + /// + public abstract bool IsReportingMouseMoves { get; internal set; } + + /// + /// Gets whether the terminal is reading input. + /// + public abstract bool IsSuspendRead { get; internal set; } + /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; @@ -608,6 +618,16 @@ public abstract class ConsoleDriver /// If simulates the Ctrl key being pressed. public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); + /// + /// Provide handling for the terminal start reporting mouse events. + /// + public abstract void StartReportingMouseMoves (); + + /// + /// Provide handling for the terminal stop reporting mouse events. + /// + public abstract void StopReportingMouseMoves (); + #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index ff4f95bdb..41e1b0862 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -18,6 +18,7 @@ internal class CursesDriver : ConsoleDriver private MouseFlags _lastMouseFlags; private UnixMainLoop _mainLoopDriver; private object _processInputToken; + private bool _isSuspendRead; public override int Cols { @@ -177,9 +178,16 @@ internal class CursesDriver : ConsoleDriver return true; } - public bool IsReportingMouseMoves { get; private set; } + public override bool IsReportingMouseMoves { get; internal set; } - public void StartReportingMouseMoves () + /// + public override bool IsSuspendRead + { + get => _isSuspendRead; + internal set => _isSuspendRead = value; + } + + public override void StartReportingMouseMoves () { if (!RunningUnitTests) { @@ -189,7 +197,7 @@ internal class CursesDriver : ConsoleDriver } } - public void StopReportingMouseMoves () + public override void StopReportingMouseMoves () { if (!RunningUnitTests) { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 73c12959f..342fe4856 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -40,6 +40,20 @@ public class FakeDriver : ConsoleDriver public static Behaviors FakeBehaviors = new (); public override bool SupportsTrueColor => false; + /// + public override bool IsReportingMouseMoves + { + get => _isReportingMouseMoves; + internal set => _isReportingMouseMoves = value; + } + + /// + public override bool IsSuspendRead + { + get => _isSuspendRead; + internal set => _isSuspendRead = value; + } + public FakeDriver () { Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH; @@ -337,6 +351,8 @@ public class FakeDriver : ConsoleDriver } private CursorVisibility _savedCursorVisibility; + private bool _isReportingMouseMoves; + private bool _isSuspendRead; private void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo) { @@ -392,6 +408,12 @@ public class FakeDriver : ConsoleDriver MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control)); } + /// + public override void StartReportingMouseMoves () { throw new NotImplementedException (); } + + /// + public override void StopReportingMouseMoves () { throw new NotImplementedException (); } + public void SetBufferSize (int width, int height) { FakeConsole.SetBufferSize (width, height); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 7d9043a32..ef82ecf8e 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1328,6 +1328,7 @@ internal class NetDriver : ConsoleDriver } private CursorVisibility? _cachedCursorVisibility; + private bool _isSuspendRead; public override void UpdateCursor () { @@ -1376,9 +1377,16 @@ internal class NetDriver : ConsoleDriver #region Mouse Handling - public bool IsReportingMouseMoves { get; private set; } + public override bool IsReportingMouseMoves { get; internal set; } - public void StartReportingMouseMoves () + /// + public override bool IsSuspendRead + { + get => _isSuspendRead; + internal set => _isSuspendRead = _suspendRead = value; + } + + public override void StartReportingMouseMoves () { if (!RunningUnitTests) { @@ -1388,7 +1396,7 @@ internal class NetDriver : ConsoleDriver } } - public void StopReportingMouseMoves () + public override void StopReportingMouseMoves () { if (!RunningUnitTests) { @@ -1396,6 +1404,19 @@ internal class NetDriver : ConsoleDriver IsReportingMouseMoves = false; } + + while (_mainLoopDriver is { _netEvents: { }} && Console.KeyAvailable) + { + _mainLoopDriver._netEvents._waitForStart.Set (); + _mainLoopDriver._netEvents._waitForStart.Reset (); + + _mainLoopDriver._netEvents._forceRead = true; + } + + if (_mainLoopDriver is { _netEvents: { } }) + { + _mainLoopDriver._netEvents._forceRead = false; + } } private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index fd5c6901c..26c01295d 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1035,6 +1035,20 @@ internal class WindowsDriver : ConsoleDriver public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal); + /// + public override bool IsReportingMouseMoves + { + get => _isReportingMouseMoves; + internal set => _isReportingMouseMoves = value; + } + + /// + public override bool IsSuspendRead + { + get => _isSuspendRead; + internal set => _isSuspendRead = value; + } + public WindowsConsole WinConsole { get; private set; } public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) @@ -1162,6 +1176,11 @@ internal class WindowsDriver : ConsoleDriver } } + /// + public override void StartReportingMouseMoves () { throw new NotImplementedException (); } + + /// + public override void StopReportingMouseMoves () { throw new NotImplementedException (); } #region Not Implemented @@ -1188,6 +1207,8 @@ internal class WindowsDriver : ConsoleDriver #region Cursor Handling private CursorVisibility? _cachedCursorVisibility; + private bool _isReportingMouseMoves; + private bool _isSuspendRead; public override void UpdateCursor () { From 7249de02efc4175d2ed2dd59616fc1eb76093199 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 13 Oct 2024 13:40:56 +0100 Subject: [PATCH 016/151] Cannot run with unit tests. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index ef82ecf8e..d20647ceb 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1405,7 +1405,7 @@ internal class NetDriver : ConsoleDriver IsReportingMouseMoves = false; } - while (_mainLoopDriver is { _netEvents: { }} && Console.KeyAvailable) + while (_mainLoopDriver is { _netEvents: { } } && Console.KeyAvailable) { _mainLoopDriver._netEvents._waitForStart.Set (); _mainLoopDriver._netEvents._waitForStart.Reset (); From f850e736a2139e936c1e954d574e83acb3927701 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 13 Oct 2024 14:00:31 +0100 Subject: [PATCH 017/151] Fix unit test. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index d20647ceb..68c814151 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1403,19 +1403,19 @@ internal class NetDriver : ConsoleDriver Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); IsReportingMouseMoves = false; - } - while (_mainLoopDriver is { _netEvents: { } } && Console.KeyAvailable) - { - _mainLoopDriver._netEvents._waitForStart.Set (); - _mainLoopDriver._netEvents._waitForStart.Reset (); + while (_mainLoopDriver is { _netEvents: { } } && Console.KeyAvailable) + { + _mainLoopDriver._netEvents._waitForStart.Set (); + _mainLoopDriver._netEvents._waitForStart.Reset (); - _mainLoopDriver._netEvents._forceRead = true; - } + _mainLoopDriver._netEvents._forceRead = true; + } - if (_mainLoopDriver is { _netEvents: { } }) - { - _mainLoopDriver._netEvents._forceRead = false; + if (_mainLoopDriver is { _netEvents: { } }) + { + _mainLoopDriver._netEvents._forceRead = false; + } } } From 67d497ce4edcd25f3dabf8a4d83d1d81d7b96ab2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 14 Oct 2024 00:21:57 +0100 Subject: [PATCH 018/151] Fixes CursesDriver stale buffer. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 41e1b0862..ce3c6686f 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -204,6 +204,14 @@ internal class CursesDriver : ConsoleDriver Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); IsReportingMouseMoves = false; + + Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer + + // Flush the input buffer to avoid reading stale input + while (Console.KeyAvailable) + { + Console.ReadKey (true); + } } } From b35b9f537a5185c1d4fe9ebf7f7d11188bc102c5 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 14 Oct 2024 16:09:28 +0100 Subject: [PATCH 019/151] Add abstract WriteAnsi into the ConsoleDriver for each driver handling ansi escape sequence. --- .../AnsiEscapeSequenceRequest.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 7 ++++++ .../CursesDriver/CursesDriver.cs | 15 +++++++++++++ .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 15 +++++++++++++ Terminal.Gui/ConsoleDrivers/NetDriver.cs | 17 +++++++++++++- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 22 ++++++++++++------- 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 1a3a40fd2..20616d65f 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -73,7 +73,7 @@ public class AnsiEscapeSequenceRequest driver!.IsSuspendRead = true; // Send the ANSI escape sequence - Console.Write (ansiRequest.Request); + driver.WriteAnsi (ansiRequest.Request); Console.Out.Flush (); // Ensure the request is sent // Read the response from stdin (response should come back as input) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e0b846560..e3dd8d5e6 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -628,6 +628,13 @@ public abstract class ConsoleDriver /// public abstract void StopReportingMouseMoves (); + /// + /// Provide handling for the terminal write ANSI escape sequence. + /// + /// The ANSI escape sequence. + /// + public abstract bool WriteAnsi (string ansi); + #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index ce3c6686f..bbd172b0f 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -215,6 +215,21 @@ internal class CursesDriver : ConsoleDriver } } + /// + public override bool WriteAnsi (string ansi) + { + try + { + Console.Out.Write (ansi); + } + catch (Exception) + { + return false; + } + + return true; + } + public override void Suspend () { StopReportingMouseMoves (); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 342fe4856..568288c84 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -414,6 +414,21 @@ public class FakeDriver : ConsoleDriver /// public override void StopReportingMouseMoves () { throw new NotImplementedException (); } + /// + public override bool WriteAnsi (string ansi) + { + try + { + Console.Out.Write (ansi); + } + catch (Exception) + { + return false; + } + + return true; + } + public void SetBufferSize (int width, int height) { FakeConsole.SetBufferSize (width, height); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 68c814151..b5e1073fd 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1419,7 +1419,22 @@ internal class NetDriver : ConsoleDriver } } - private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) + /// + public override bool WriteAnsi (string ansi) + { + try + { + Console.Out.Write (ansi); + } + catch (Exception) + { + return false; + } + + return true; + } + + private MouseEvent ToDriverMouse (NetEvents.MouseEvent me) { //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 26c01295d..6670a4862 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -134,7 +134,7 @@ internal class WindowsConsole return result; } - public bool WriteANSI (string ansi) + internal bool WriteANSI (string ansi) { return WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, nint.Zero); } @@ -1177,13 +1177,17 @@ internal class WindowsDriver : ConsoleDriver } /// - public override void StartReportingMouseMoves () { throw new NotImplementedException (); } - - /// - public override void StopReportingMouseMoves () { throw new NotImplementedException (); } + public override bool WriteAnsi (string ansi) + { + return WinConsole?.WriteANSI (ansi) ?? false; + } #region Not Implemented + public override void StartReportingMouseMoves () { throw new NotImplementedException (); } + + public override void StopReportingMouseMoves () { throw new NotImplementedException (); } + public override void Suspend () { throw new NotImplementedException (); } #endregion @@ -1235,7 +1239,7 @@ internal class WindowsDriver : ConsoleDriver { var sb = new StringBuilder (); sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); - WinConsole?.WriteANSI (sb.ToString ()); + WriteAnsi (sb.ToString ()); } if (_cachedCursorVisibility is { }) @@ -1271,7 +1275,8 @@ internal class WindowsDriver : ConsoleDriver { var sb = new StringBuilder (); sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); - return WinConsole?.WriteANSI (sb.ToString ()) ?? false; + + return WriteAnsi (sb.ToString ()); } } @@ -1286,7 +1291,8 @@ internal class WindowsDriver : ConsoleDriver { var sb = new StringBuilder (); sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); - return WinConsole?.WriteANSI (sb.ToString ()) ?? false; + + return WriteAnsi (sb.ToString ()); } //if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) From a2e4d82fd72434d55ec7edfa0fb2a9cb187de549 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 14 Oct 2024 16:27:44 +0100 Subject: [PATCH 020/151] Add WriteAnsiDefault method to common code. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 14 ++++++++++++++ .../ConsoleDrivers/CursesDriver/CursesDriver.cs | 11 +---------- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 11 +---------- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 11 +---------- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e3dd8d5e6..a2ec4e27a 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -635,6 +635,20 @@ public abstract class ConsoleDriver /// public abstract bool WriteAnsi (string ansi); + internal bool WriteAnsiDefault (string ansi) + { + try + { + Console.Out.Write (ansi); + } + catch (Exception) + { + return false; + } + + return true; + } + #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index bbd172b0f..c0ad1974e 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -218,16 +218,7 @@ internal class CursesDriver : ConsoleDriver /// public override bool WriteAnsi (string ansi) { - try - { - Console.Out.Write (ansi); - } - catch (Exception) - { - return false; - } - - return true; + return WriteAnsiDefault (ansi); } public override void Suspend () diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 568288c84..dc46b055b 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -417,16 +417,7 @@ public class FakeDriver : ConsoleDriver /// public override bool WriteAnsi (string ansi) { - try - { - Console.Out.Write (ansi); - } - catch (Exception) - { - return false; - } - - return true; + return WriteAnsiDefault (ansi); } public void SetBufferSize (int width, int height) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index b5e1073fd..d5d361122 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1422,16 +1422,7 @@ internal class NetDriver : ConsoleDriver /// public override bool WriteAnsi (string ansi) { - try - { - Console.Out.Write (ansi); - } - catch (Exception) - { - return false; - } - - return true; + return WriteAnsiDefault (ansi); } private MouseEvent ToDriverMouse (NetEvents.MouseEvent me) From cc1d6685c836ea961136359a26d79536bf8fa6af Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 14 Oct 2024 17:00:50 +0100 Subject: [PATCH 021/151] Fix Window Terminal Preview using WindowsDriver. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 6670a4862..188d0c653 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -30,7 +30,7 @@ internal class WindowsConsole public const int STD_INPUT_HANDLE = -10; private readonly nint _inputHandle; - private readonly nint _outputHandle; + private nint _outputHandle; private nint _screenBuffer; private readonly uint _originalConsoleMode; private CursorVisibility? _initialCursorVisibility; @@ -286,6 +286,14 @@ internal class WindowsConsole ConsoleMode = _originalConsoleMode; + _outputHandle = CreateConsoleScreenBuffer ( + DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + ShareMode.FileShareRead | ShareMode.FileShareWrite, + nint.Zero, + 1, + nint.Zero + ); + if (!SetConsoleActiveScreenBuffer (_outputHandle)) { int err = Marshal.GetLastWin32Error (); From 5ce39961ad571a8da328855d1221a85dbcb09da7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 14 Oct 2024 17:36:20 +0100 Subject: [PATCH 022/151] Prevents throwing if selected item is equal to minus 1. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 8ae89e346..4d3f4dd0b 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -44,6 +44,11 @@ public sealed class AnsiEscapeSequenceRequests : Scenario cbRequests.SelectedItemChanged += (s, e) => { + if (cbRequests.SelectedItem == -1) + { + return; + } + var selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem]; AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null; switch (selAnsiEscapeSequenceRequestName) From d96394152247510304b0e9daee349f2d380b06bc Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 15 Oct 2024 17:27:23 +0100 Subject: [PATCH 023/151] Preparing NetDriver to handle ansi response on demand. --- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 71 +++++----- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 12 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 12 +- UnitTests/Input/EscSeqReqTests.cs | 54 +++++--- UnitTests/Input/EscSeqUtilsTests.cs | 131 +++++++++--------- 5 files changed, 141 insertions(+), 139 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index 29ef5afa7..bd2c1372e 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui; +#nullable enable + +namespace Terminal.Gui; /// /// Represents the status of an ANSI escape sequence request made to the terminal using @@ -8,22 +10,21 @@ public class EscSeqReqStatus { /// Creates a new state of escape sequence request. - /// The terminator. - /// The number of requests. - public EscSeqReqStatus (string terminator, int numReq) + /// The object. + public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { - Terminator = terminator; - NumRequests = NumOutstanding = numReq; + AnsiRequest = ansiRequest; + NumRequests = NumOutstanding = 1; } /// Gets the number of unfinished requests. public int NumOutstanding { get; set; } /// Gets the number of requests. - public int NumRequests { get; } + public int NumRequests { get; set; } /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). - public string Terminator { get; } + public AnsiEscapeSequenceRequest AnsiRequest { get; } } // TODO: This class is a singleton. It should use the singleton pattern. @@ -37,24 +38,28 @@ public class EscSeqRequests public List Statuses { get; } = new (); /// - /// Adds a new request for the ANSI Escape Sequence defined by . Adds a + /// Adds a new request for the ANSI Escape Sequence defined by . Adds a /// instance to list. /// - /// The terminator. - /// The number of requests. - public void Add (string terminator, int numReq = 1) + /// The object. + public void Add (AnsiEscapeSequenceRequest ansiRequest) { lock (Statuses) { - EscSeqReqStatus found = Statuses.Find (x => x.Terminator == terminator); + EscSeqReqStatus? found = Statuses.Find (x => x.AnsiRequest.Terminator == ansiRequest.Terminator); if (found is null) { - Statuses.Add (new EscSeqReqStatus (terminator, numReq)); + Statuses.Add (new (ansiRequest)); } - else if (found is { } && found.NumOutstanding < found.NumRequests) + else if (found.NumOutstanding < found.NumRequests) { - found.NumOutstanding = Math.Min (found.NumOutstanding + numReq, found.NumRequests); + found.NumOutstanding = Math.Min (found.NumOutstanding + 1, found.NumRequests); + } + else + { + found.NumRequests++; + found.NumOutstanding++; } } } @@ -64,54 +69,42 @@ public class EscSeqRequests /// list. /// /// + /// /// if exist, otherwise. - public bool HasResponse (string terminator) + public bool HasResponse (string terminator, out EscSeqReqStatus? seqReqStatus) { lock (Statuses) { - EscSeqReqStatus found = Statuses.Find (x => x.Terminator == terminator); + EscSeqReqStatus? found = Statuses.Find (x => x.AnsiRequest.Terminator == terminator); + seqReqStatus = found; - if (found is null) - { - return false; - } - - if (found is { NumOutstanding: > 0 }) - { - return true; - } - - // BUGBUG: Why does an API that returns a bool remove the entry from the list? - // NetDriver and Unit tests never exercise this line of code. Maybe Curses does? - Statuses.Remove (found); - - return false; + return found is { NumOutstanding: > 0 }; } } /// - /// Removes a request defined by . If a matching is + /// Removes a request defined by . If a matching is /// found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented. /// If the number of outstanding requests is 0, the is removed from /// . /// - /// The terminating string. - public void Remove (string terminator) + /// The object. + public void Remove (EscSeqReqStatus? seqReqStatus) { lock (Statuses) { - EscSeqReqStatus found = Statuses.Find (x => x.Terminator == terminator); + EscSeqReqStatus? found = Statuses.Find (x => x == seqReqStatus); if (found is null) { return; } - if (found is { } && found.NumOutstanding == 0) + if (found is { NumOutstanding: 0 }) { Statuses.Remove (found); } - else if (found is { } && found.NumOutstanding > 0) + else if (found is { NumOutstanding: > 0 }) { found.NumOutstanding--; diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index d32a8e01f..698965d32 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -1,3 +1,4 @@ +#nullable enable namespace Terminal.Gui; /// @@ -170,7 +171,7 @@ public static class EscSeqUtils /// Indicates if the escape sequence is a mouse event. /// The button state. /// The position. - /// Indicates if the escape sequence is a response to a request. + /// The object. /// The handler that will process the event. public static void DecodeEscSeq ( EscSeqRequests escSeqRequests, @@ -185,7 +186,7 @@ public static class EscSeqUtils out bool isMouse, out List buttonState, out Point pos, - out bool isResponse, + out EscSeqReqStatus? seqReqStatus, Action continuousButtonPressedHandler ) { @@ -194,7 +195,7 @@ public static class EscSeqUtils isMouse = false; buttonState = new List { 0 }; pos = default (Point); - isResponse = false; + seqReqStatus = null; char keyChar = '\0'; switch (c1Control) @@ -262,10 +263,9 @@ public static class EscSeqUtils return; } - if (escSeqRequests is { } && escSeqRequests.HasResponse (terminator)) + if (escSeqRequests is { } && escSeqRequests.HasResponse (terminator, out seqReqStatus)) { - isResponse = true; - escSeqRequests.Remove (terminator); + escSeqRequests.Remove (seqReqStatus); return; } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index d5d361122..adad24b6e 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -2,7 +2,6 @@ // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. // -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; @@ -202,7 +201,7 @@ internal class NetEvents : IDisposable { // if there is a key available, return it without waiting // (or dispatching work to the thread queue) - if (Console.KeyAvailable && !_suspendRead) + if (Console.KeyAvailable) { return Console.ReadKey (intercept); } @@ -211,7 +210,7 @@ internal class NetEvents : IDisposable { Task.Delay (100, cancellationToken).Wait (cancellationToken); - if (Console.KeyAvailable && !_suspendRead) + if (Console.KeyAvailable) { return Console.ReadKey (intercept); } @@ -223,7 +222,6 @@ internal class NetEvents : IDisposable } internal bool _forceRead; - internal static bool _suspendRead; private void ProcessInputQueue () { @@ -432,7 +430,7 @@ internal class NetEvents : IDisposable out bool isMouse, out List mouseFlags, out Point pos, - out bool isReq, + out EscSeqReqStatus seqReqStatus, (f, p) => HandleMouseEvent (MapMouseFlags (f), p) ); @@ -446,7 +444,7 @@ internal class NetEvents : IDisposable return; } - if (isReq) + if (seqReqStatus is { }) { HandleRequestResponseEvent (c1Control, code, values, terminating); @@ -1383,7 +1381,7 @@ internal class NetDriver : ConsoleDriver public override bool IsSuspendRead { get => _isSuspendRead; - internal set => _isSuspendRead = _suspendRead = value; + internal set => _isSuspendRead = value; } public override void StartReportingMouseMoves () diff --git a/UnitTests/Input/EscSeqReqTests.cs b/UnitTests/Input/EscSeqReqTests.cs index 6b73b2af0..d1499eabd 100644 --- a/UnitTests/Input/EscSeqReqTests.cs +++ b/UnitTests/Input/EscSeqReqTests.cs @@ -6,30 +6,31 @@ public class EscSeqReqTests public void Add_Tests () { var escSeqReq = new EscSeqRequests (); - escSeqReq.Add ("t"); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests); Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); - escSeqReq.Add ("t", 2); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); - Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests); - Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); - - escSeqReq = new EscSeqRequests (); - escSeqReq.Add ("t", 2); - Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding); - escSeqReq.Add ("t", 3); + escSeqReq = new (); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding); + + escSeqReq.Add (new () { Request = "", Terminator = "t" }); + Assert.Single (escSeqReq.Statuses); + Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); + Assert.Equal (3, escSeqReq.Statuses [^1].NumRequests); + Assert.Equal (3, escSeqReq.Statuses [^1].NumOutstanding); } [Fact] @@ -44,18 +45,22 @@ public class EscSeqReqTests public void Remove_Tests () { var escSeqReq = new EscSeqRequests (); - escSeqReq.Add ("t"); - escSeqReq.Remove ("t"); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); + escSeqReq.HasResponse ("t", out EscSeqReqStatus seqReqStatus); + escSeqReq.Remove (seqReqStatus); Assert.Empty (escSeqReq.Statuses); - escSeqReq.Add ("t", 2); - escSeqReq.Remove ("t"); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); + escSeqReq.HasResponse ("t", out seqReqStatus); + escSeqReq.Remove (seqReqStatus); Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); - escSeqReq.Remove ("t"); + escSeqReq.HasResponse ("t", out seqReqStatus); + escSeqReq.Remove (seqReqStatus); Assert.Empty (escSeqReq.Statuses); } @@ -63,10 +68,13 @@ public class EscSeqReqTests public void Requested_Tests () { var escSeqReq = new EscSeqRequests (); - Assert.False (escSeqReq.HasResponse ("t")); + Assert.False (escSeqReq.HasResponse ("t", out EscSeqReqStatus seqReqStatus)); + Assert.Null (seqReqStatus); - escSeqReq.Add ("t"); - Assert.False (escSeqReq.HasResponse ("r")); - Assert.True (escSeqReq.HasResponse ("t")); + escSeqReq.Add (new () { Request = "", Terminator = "t" }); + Assert.False (escSeqReq.HasResponse ("r", out seqReqStatus)); + Assert.Null (seqReqStatus); + Assert.True (escSeqReq.HasResponse ("t", out seqReqStatus)); + Assert.NotNull (seqReqStatus); } } diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 9b811f002..7b6e6538e 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui.InputTests; +using JetBrains.Annotations; + +namespace Terminal.Gui.InputTests; public class EscSeqUtilsTests { @@ -9,7 +11,8 @@ public class EscSeqUtilsTests private ConsoleKeyInfo [] _cki; private EscSeqRequests _escSeqReqProc; private bool _isKeyMouse; - private bool _isReq; + [CanBeNull] + private EscSeqReqStatus _seqReqStatus; private ConsoleKey _key; private ConsoleModifiers _mod; private List _mouseFlags; @@ -38,7 +41,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -50,9 +53,9 @@ public class EscSeqUtilsTests Assert.Null (_values); Assert.Null (_terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -73,7 +76,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -85,9 +88,9 @@ public class EscSeqUtilsTests Assert.Null (_values); Assert.Equal ("\u0012", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -108,7 +111,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -120,9 +123,9 @@ public class EscSeqUtilsTests Assert.Null (_values); Assert.Equal ("r", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -148,7 +151,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -161,9 +164,9 @@ public class EscSeqUtilsTests Assert.Null (_values [0]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -194,7 +197,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -208,9 +211,9 @@ public class EscSeqUtilsTests Assert.Equal ("2", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -240,7 +243,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -254,9 +257,9 @@ public class EscSeqUtilsTests Assert.Equal ("3", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -286,7 +289,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -300,9 +303,9 @@ public class EscSeqUtilsTests Assert.Equal ("4", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -332,7 +335,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -346,9 +349,9 @@ public class EscSeqUtilsTests Assert.Equal ("5", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -378,7 +381,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -392,9 +395,9 @@ public class EscSeqUtilsTests Assert.Equal ("6", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -424,7 +427,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -438,9 +441,9 @@ public class EscSeqUtilsTests Assert.Equal ("7", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -470,7 +473,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -484,9 +487,9 @@ public class EscSeqUtilsTests Assert.Equal ("8", _values [^1]); Assert.Equal ("R", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -519,7 +522,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -534,9 +537,9 @@ public class EscSeqUtilsTests Assert.Equal ("3", _values [^1]); Assert.Equal ("M", _terminating); Assert.True (_isKeyMouse); - Assert.Equal (new() { MouseFlags.Button1Pressed }, _mouseFlags); + Assert.Equal (new () { MouseFlags.Button1Pressed }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -569,7 +572,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -587,11 +590,11 @@ public class EscSeqUtilsTests Assert.Equal (2, _mouseFlags.Count); Assert.Equal ( - new() { MouseFlags.Button1Released, MouseFlags.Button1Clicked }, + new () { MouseFlags.Button1Released, MouseFlags.Button1Clicked }, _mouseFlags ); Assert.Equal (new (1, 2), _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -624,7 +627,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -639,9 +642,9 @@ public class EscSeqUtilsTests Assert.Equal ("3", _values [^1]); Assert.Equal ("M", _terminating); Assert.True (_isKeyMouse); - Assert.Equal (new() { MouseFlags.Button1DoubleClicked }, _mouseFlags); + Assert.Equal (new () { MouseFlags.Button1DoubleClicked }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); ClearAll (); @@ -672,7 +675,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -687,9 +690,9 @@ public class EscSeqUtilsTests Assert.Equal ("3", _values [^1]); Assert.Equal ("M", _terminating); Assert.True (_isKeyMouse); - Assert.Equal (new() { MouseFlags.Button1TripleClicked }, _mouseFlags); + Assert.Equal (new () { MouseFlags.Button1TripleClicked }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), WantContinuousButtonPressed = true }; var top = new Toplevel (); @@ -727,7 +730,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -742,9 +745,9 @@ public class EscSeqUtilsTests Assert.Equal ("3", _values [^1]); Assert.Equal ("M", _terminating); Assert.True (_isKeyMouse); - Assert.Equal (new() { MouseFlags.Button1Pressed }, _mouseFlags); + Assert.Equal (new () { MouseFlags.Button1Pressed }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Application.Iteration += (s, a) => { @@ -796,7 +799,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Null (_escSeqReqProc); @@ -811,9 +814,9 @@ public class EscSeqUtilsTests Assert.Equal ("3", _values [^1]); Assert.Equal ("m", _terminating); Assert.True (_isKeyMouse); - Assert.Equal (new() { MouseFlags.Button1Released }, _mouseFlags); + Assert.Equal (new () { MouseFlags.Button1Released }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.False (_isReq); + Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -821,7 +824,7 @@ public class EscSeqUtilsTests Assert.Null (_escSeqReqProc); _escSeqReqProc = new (); - _escSeqReqProc.Add ("t"); + _escSeqReqProc.Add (new () { Request = "", Terminator = "t" }); _cki = new ConsoleKeyInfo [] { @@ -838,7 +841,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); Assert.Single (_escSeqReqProc.Statuses); - Assert.Equal ("t", _escSeqReqProc.Statuses [^1].Terminator); + Assert.Equal ("t", _escSeqReqProc.Statuses [^1].AnsiRequest.Terminator); EscSeqUtils.DecodeEscSeq ( _escSeqReqProc, @@ -853,7 +856,7 @@ public class EscSeqUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _isReq, + out _seqReqStatus, ProcessContinuousButtonPressed ); Assert.Empty (_escSeqReqProc.Statuses); @@ -868,9 +871,9 @@ public class EscSeqUtilsTests Assert.Equal ("20", _values [^1]); Assert.Equal ("t", _terminating); Assert.False (_isKeyMouse); - Assert.Equal (new() { 0 }, _mouseFlags); + Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.True (_isReq); + Assert.NotNull (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); } @@ -1073,7 +1076,7 @@ public class EscSeqUtilsTests new ('M', 0, false, false, false) }; EscSeqUtils.GetMouse (cki, out List mouseFlags, out Point pos, ProcessContinuousButtonPressed); - Assert.Equal (new() { MouseFlags.Button1Pressed }, mouseFlags); + Assert.Equal (new () { MouseFlags.Button1Pressed }, mouseFlags); Assert.Equal (new (1, 2), pos); cki = new ConsoleKeyInfo [] @@ -1092,7 +1095,7 @@ public class EscSeqUtilsTests Assert.Equal (2, mouseFlags.Count); Assert.Equal ( - new() { MouseFlags.Button1Released, MouseFlags.Button1Clicked }, + new () { MouseFlags.Button1Released, MouseFlags.Button1Clicked }, mouseFlags ); Assert.Equal (new (1, 2), pos); @@ -1110,7 +1113,7 @@ public class EscSeqUtilsTests new ('M', 0, false, false, false) }; EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); - Assert.Equal (new() { MouseFlags.Button1DoubleClicked }, mouseFlags); + Assert.Equal (new () { MouseFlags.Button1DoubleClicked }, mouseFlags); Assert.Equal (new (1, 2), pos); cki = new ConsoleKeyInfo [] @@ -1126,7 +1129,7 @@ public class EscSeqUtilsTests new ('M', 0, false, false, false) }; EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); - Assert.Equal (new() { MouseFlags.Button1TripleClicked }, mouseFlags); + Assert.Equal (new () { MouseFlags.Button1TripleClicked }, mouseFlags); Assert.Equal (new (1, 2), pos); cki = new ConsoleKeyInfo [] @@ -1142,7 +1145,7 @@ public class EscSeqUtilsTests new ('m', 0, false, false, false) }; EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); - Assert.Equal (new() { MouseFlags.Button1Released }, mouseFlags); + Assert.Equal (new () { MouseFlags.Button1Released }, mouseFlags); Assert.Equal (new (1, 2), pos); } @@ -1168,7 +1171,7 @@ public class EscSeqUtilsTests _terminating = default (string); _values = default (string []); _isKeyMouse = default (bool); - _isReq = default (bool); + _seqReqStatus = null; _mouseFlags = default (List); _pos = default (Point); _arg1 = default (MouseFlags); From 0de826294868822a5a2a9eaddad878800765eb82 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 14 Oct 2024 18:30:21 -0600 Subject: [PATCH 024/151] View.Mouse cleanup - WIP --- Terminal.Gui/View/View.Mouse.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs index a76de77b3..eb6f4d6d3 100644 --- a/Terminal.Gui/View/View.Mouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -318,6 +318,14 @@ public partial class View // Mouse APIs /// public event EventHandler? MouseEvent; + /// Raised when a mouse event occurs. + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler? MouseEvent; + #endregion Low Level Mouse Events #region Mouse Click Events From c3187482fc0345c65336fe8c67562111ef2ba20a Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 15 Oct 2024 06:52:05 -0600 Subject: [PATCH 025/151] View.Mouse cleanup - WIP3 --- Terminal.Gui/Views/ComboBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 996056068..72056992d 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -880,7 +880,7 @@ public class ComboBox : View, IDesignable return true; } - return res; + return false; } public override void OnDrawContent (Rectangle viewport) From 2ad9887ecba938197422fb43bc4bb0dfa45524a0 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 15 Oct 2024 06:56:53 -0600 Subject: [PATCH 026/151] View.Mouse cleanup - Fixed combobox --- Terminal.Gui/Views/ComboBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 72056992d..996056068 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -880,7 +880,7 @@ public class ComboBox : View, IDesignable return true; } - return false; + return res; } public override void OnDrawContent (Rectangle viewport) From ec14b62adedca7708f445aea6b8535a77e9d3d8d Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 15 Oct 2024 07:40:42 -0600 Subject: [PATCH 027/151] Merged MouseEvent and MouseEventEventArgs into MouseEventArgs --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index adad24b6e..1c780e291 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1423,7 +1423,7 @@ internal class NetDriver : ConsoleDriver return WriteAnsiDefault (ansi); } - private MouseEvent ToDriverMouse (NetEvents.MouseEvent me) + private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) { //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); From 9dcfe02087d157ca9ee65d577c5b96a71fa188c4 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 15 Oct 2024 10:48:00 -0600 Subject: [PATCH 028/151] Fixed Time/DateField crash --- Terminal.Gui/Views/DateField.cs | 2 +- Terminal.Gui/Views/TimeField.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 9490a7b28..641b5d314 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -167,7 +167,7 @@ public class DateField : TextField newPoint = 1; } - if (newPoint != point) + //if (newPoint != point) { CursorPosition = newPoint; } diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index ecc94a7be..abd4048b2 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -225,7 +225,7 @@ public class TimeField : TextField newPoint = 1; } - if (newPoint != point) + //if (newPoint != point) { CursorPosition = newPoint; } From 59396aeb75f86adf7348bcbe8f7b3656e497756f Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 15 Oct 2024 10:55:23 -0600 Subject: [PATCH 029/151] Fixed Time/DateField crash 2 --- Terminal.Gui/Views/DateField.cs | 2 +- Terminal.Gui/Views/TimeField.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 641b5d314..9490a7b28 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -167,7 +167,7 @@ public class DateField : TextField newPoint = 1; } - //if (newPoint != point) + if (newPoint != point) { CursorPosition = newPoint; } diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index abd4048b2..ecc94a7be 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -225,7 +225,7 @@ public class TimeField : TextField newPoint = 1; } - //if (newPoint != point) + if (newPoint != point) { CursorPosition = newPoint; } From 49096fa389536636393fb858443fd787ab6d70b9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 15 Oct 2024 19:32:13 +0100 Subject: [PATCH 030/151] Fix merge errors. --- Terminal.Gui/View/View.Mouse.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs index eb6f4d6d3..a76de77b3 100644 --- a/Terminal.Gui/View/View.Mouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -318,14 +318,6 @@ public partial class View // Mouse APIs /// public event EventHandler? MouseEvent; - /// Raised when a mouse event occurs. - /// - /// - /// The coordinates are relative to . - /// - /// - public event EventHandler? MouseEvent; - #endregion Low Level Mouse Events #region Mouse Click Events From 884011e99cdb76fe330af2d80bf3ca1343350dee Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 16 Oct 2024 01:30:02 +0100 Subject: [PATCH 031/151] Improving WriteAnsi method to return the response. --- .../AnsiEscapeSequenceRequest.cs | 38 ++--- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 35 +++- .../CursesDriver/CursesDriver.cs | 9 +- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 72 +++------ .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 4 +- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 9 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 152 +++++++++++------- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 17 +- UnitTests/Input/EscSeqReqTests.cs | 26 +-- UnitTests/Input/EscSeqUtilsTests.cs | 2 +- 10 files changed, 194 insertions(+), 170 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 20616d65f..942a5f828 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -16,6 +16,11 @@ public class AnsiEscapeSequenceRequest /// public required string Request { get; init; } + /// + /// Response received from the request. + /// + public string Response { get; internal set; } = string.Empty; + /// /// Invoked when the console responds with an ANSI response code that matches the /// @@ -53,7 +58,6 @@ public class AnsiEscapeSequenceRequest /// A with the response, error, terminator and value. public static bool TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) { - var response = new StringBuilder (); var error = new StringBuilder (); var savedIsReportingMouseMoves = false; ConsoleDriver? driver = null; @@ -73,35 +77,13 @@ public class AnsiEscapeSequenceRequest driver!.IsSuspendRead = true; // Send the ANSI escape sequence - driver.WriteAnsi (ansiRequest.Request); - Console.Out.Flush (); // Ensure the request is sent - - // Read the response from stdin (response should come back as input) - Thread.Sleep (100); // Allow time for the terminal to respond - - // Read input until no more characters are available or the terminator is encountered - while (Console.KeyAvailable) - { - // Peek the next key - ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console - - // Append the current key to the response - response.Append (keyInfo.KeyChar); - - // Read until no key is available if no terminator was specified or - // check if the key is terminator (ANSI escape sequence ends) - if (!string.IsNullOrEmpty (ansiRequest.Terminator) && keyInfo.KeyChar == ansiRequest.Terminator [^1]) - { - // Break out of the loop when terminator is found - break; - } - } + ansiRequest.Response = driver.WriteAnsi (ansiRequest); if (string.IsNullOrEmpty (ansiRequest.Terminator)) { error.AppendLine ("Terminator request is empty."); } - else if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1])) + else if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) { throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'"); } @@ -114,7 +96,7 @@ public class AnsiEscapeSequenceRequest { if (string.IsNullOrEmpty (error.ToString ())) { - (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ()); + (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ()); } if (savedIsReportingMouseMoves) @@ -126,8 +108,8 @@ public class AnsiEscapeSequenceRequest AnsiEscapeSequenceResponse ansiResponse = new () { - Response = response.ToString (), Error = error.ToString (), - Terminator = string.IsNullOrEmpty (response.ToString ()) ? "" : response.ToString () [^1].ToString (), Value = values [0] + Response = ansiRequest.Response, Error = error.ToString (), + Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), Value = values [0] }; // Invoke the event if it's subscribed diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index a2ec4e27a..8252ea5e5 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -629,17 +629,22 @@ public abstract class ConsoleDriver public abstract void StopReportingMouseMoves (); /// - /// Provide handling for the terminal write ANSI escape sequence. + /// Provide handling for the terminal write ANSI escape sequence request. /// - /// The ANSI escape sequence. + /// The object. /// - public abstract bool WriteAnsi (string ansi); + public abstract string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest); internal bool WriteAnsiDefault (string ansi) { try { Console.Out.Write (ansi); + Console.Out.Flush (); // Ensure the request is sent + + // Read the response from stdin (response should come back as input) + Thread.Sleep (100); // Allow time for the terminal to respond + } catch (Exception) { @@ -649,6 +654,30 @@ public abstract class ConsoleDriver return true; } + internal string ReadAnsiDefault (AnsiEscapeSequenceRequest ansiRequest) + { + var response = new StringBuilder (); + + while (Console.KeyAvailable) + { + // Peek the next key + ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console + + // Append the current key to the response + response.Append (keyInfo.KeyChar); + + // Read until no key is available if no terminator was specified or + // check if the key is terminator (ANSI escape sequence ends) + if (!string.IsNullOrEmpty (ansiRequest.Terminator) && keyInfo.KeyChar == ansiRequest.Terminator [^1]) + { + // Break out of the loop when terminator is found + break; + } + } + + return response.ToString (); + } + #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index c0ad1974e..6d1e3cbc3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -216,9 +216,14 @@ internal class CursesDriver : ConsoleDriver } /// - public override bool WriteAnsi (string ansi) + public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) { - return WriteAnsiDefault (ansi); + if (WriteAnsiDefault (ansiRequest.Request)) + { + return ReadAnsiDefault (ansiRequest); + } + + return string.Empty; } public override void Suspend () diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index bd2c1372e..178f8f7a9 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -11,17 +11,7 @@ public class EscSeqReqStatus { /// Creates a new state of escape sequence request. /// The object. - public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) - { - AnsiRequest = ansiRequest; - NumRequests = NumOutstanding = 1; - } - - /// Gets the number of unfinished requests. - public int NumOutstanding { get; set; } - - /// Gets the number of requests. - public int NumRequests { get; set; } + public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; } /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). public AnsiEscapeSequenceRequest AnsiRequest { get; } @@ -34,9 +24,6 @@ public class EscSeqReqStatus /// public class EscSeqRequests { - /// Gets the list. - public List Statuses { get; } = new (); - /// /// Adds a new request for the ANSI Escape Sequence defined by . Adds a /// instance to list. @@ -46,21 +33,10 @@ public class EscSeqRequests { lock (Statuses) { - EscSeqReqStatus? found = Statuses.Find (x => x.AnsiRequest.Terminator == ansiRequest.Terminator); - - if (found is null) - { - Statuses.Add (new (ansiRequest)); - } - else if (found.NumOutstanding < found.NumRequests) - { - found.NumOutstanding = Math.Min (found.NumOutstanding + 1, found.NumRequests); - } - else - { - found.NumRequests++; - found.NumOutstanding++; - } + Statuses.Enqueue (new (ansiRequest)); + Console.Out.Write (ansiRequest.Request); + Console.Out.Flush (); + Thread.Sleep (100); // Allow time for the terminal to respond } } @@ -75,10 +51,18 @@ public class EscSeqRequests { lock (Statuses) { - EscSeqReqStatus? found = Statuses.Find (x => x.AnsiRequest.Terminator == terminator); - seqReqStatus = found; + Statuses.TryPeek (out seqReqStatus); - return found is { NumOutstanding: > 0 }; + var result = seqReqStatus?.AnsiRequest.Terminator == terminator; + + if (result) + { + return true; + } + + seqReqStatus = null; + + return false; } } @@ -93,26 +77,10 @@ public class EscSeqRequests { lock (Statuses) { - EscSeqReqStatus? found = Statuses.Find (x => x == seqReqStatus); - - if (found is null) - { - return; - } - - if (found is { NumOutstanding: 0 }) - { - Statuses.Remove (found); - } - else if (found is { NumOutstanding: > 0 }) - { - found.NumOutstanding--; - - if (found.NumOutstanding == 0) - { - Statuses.Remove (found); - } - } + Statuses.Dequeue (); } } + + /// Gets the list. + public Queue Statuses { get; } = new (); } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 698965d32..a8c830f7e 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -174,7 +174,7 @@ public static class EscSeqUtils /// The object. /// The handler that will process the event. public static void DecodeEscSeq ( - EscSeqRequests escSeqRequests, + EscSeqRequests? escSeqRequests, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, @@ -497,7 +497,7 @@ public static class EscSeqUtils // PERF: This is expensive public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki) { - char [] kChar = { }; + char [] kChar = []; var length = 0; foreach (ConsoleKeyInfo kc in cki) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index dc46b055b..10a53234d 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -415,9 +415,14 @@ public class FakeDriver : ConsoleDriver public override void StopReportingMouseMoves () { throw new NotImplementedException (); } /// - public override bool WriteAnsi (string ansi) + public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) { - return WriteAnsiDefault (ansi); + if (WriteAnsiDefault (ansiRequest.Request)) + { + return ReadAnsiDefault (ansiRequest); + } + + return string.Empty; } public void SetBufferSize (int width, int height) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 1c780e291..2f9f2a031 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -446,7 +446,17 @@ internal class NetEvents : IDisposable if (seqReqStatus is { }) { - HandleRequestResponseEvent (c1Control, code, values, terminating); + //HandleRequestResponseEvent (c1Control, code, values, terminating); + StringBuilder sb = new (); + + foreach (ConsoleKeyInfo keyChar in cki) + { + sb.Append (keyChar.KeyChar); + } + + seqReqStatus.AnsiRequest.Response = sb.ToString (); + + ((NetDriver)_consoleDriver)._waitAnsiResponse.Set (); return; } @@ -590,64 +600,64 @@ internal class NetEvents : IDisposable private Point _lastCursorPosition; - private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) - { - if (terminating == + //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + //{ + // if (terminating == - // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. - // The observation is correct because the response isn't immediate and this is useless - EscSeqUtils.CSI_RequestCursorPositionReport.Terminator) - { - var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; + // // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. + // // The observation is correct because the response isn't immediate and this is useless + // EscSeqUtils.CSI_RequestCursorPositionReport.Terminator) + // { + // var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; - if (_lastCursorPosition.Y != point.Y) - { - _lastCursorPosition = point; - var eventType = EventType.WindowPosition; - var winPositionEv = new WindowPositionEvent { CursorPosition = point }; + // if (_lastCursorPosition.Y != point.Y) + // { + // _lastCursorPosition = point; + // var eventType = EventType.WindowPosition; + // var winPositionEv = new WindowPositionEvent { CursorPosition = point }; - _inputQueue.Enqueue ( - new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } - ); - } - else - { - return; - } - } - else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator) - { - if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value) - { - EnqueueWindowSizeEvent ( - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0), - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0) - ); - } - else - { - EnqueueRequestResponseEvent (c1Control, code, values, terminating); - } - } - else - { - EnqueueRequestResponseEvent (c1Control, code, values, terminating); - } + // _inputQueue.Enqueue ( + // new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } + // ); + // } + // else + // { + // return; + // } + // } + // else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator) + // { + // if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value) + // { + // EnqueueWindowSizeEvent ( + // Math.Max (int.Parse (values [1]), 0), + // Math.Max (int.Parse (values [2]), 0), + // Math.Max (int.Parse (values [1]), 0), + // Math.Max (int.Parse (values [2]), 0) + // ); + // } + // else + // { + // EnqueueRequestResponseEvent (c1Control, code, values, terminating); + // } + // } + // else + // { + // EnqueueRequestResponseEvent (c1Control, code, values, terminating); + // } - _inputReady.Set (); - } + // _inputReady.Set (); + //} - private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) - { - var eventType = EventType.RequestResponse; - var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) }; + //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + //{ + // var eventType = EventType.RequestResponse; + // var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) }; - _inputQueue.Enqueue ( - new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv } - ); - } + // _inputQueue.Enqueue ( + // new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv } + // ); + //} private void HandleMouseEvent (MouseButtonState buttonState, Point pos) { @@ -1042,6 +1052,11 @@ internal class NetDriver : ConsoleDriver StopReportingMouseMoves (); + _ansiResponseTokenSource?.Cancel (); + _ansiResponseTokenSource?.Dispose (); + + _waitAnsiResponse?.Dispose (); + if (!RunningUnitTests) { Console.ResetColor (); @@ -1417,10 +1432,37 @@ internal class NetDriver : ConsoleDriver } } + internal ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + /// - public override bool WriteAnsi (string ansi) + public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) { - return WriteAnsiDefault (ansi); + _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); + + try + { + if (!_ansiResponseTokenSource.IsCancellationRequested && Console.KeyAvailable) + { + _mainLoopDriver._netEvents._forceRead = true; + + _mainLoopDriver._netEvents._waitForStart.Set (); + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return string.Empty; + } + finally + { + _waitAnsiResponse.Reset (); + } + + _mainLoopDriver._netEvents._forceRead = false; + + return ansiRequest.Response; } private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 188d0c653..50ac3c956 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1185,9 +1185,14 @@ internal class WindowsDriver : ConsoleDriver } /// - public override bool WriteAnsi (string ansi) + public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) { - return WinConsole?.WriteANSI (ansi) ?? false; + if (WinConsole?.WriteANSI (ansiRequest.Request) == true) + { + return ReadAnsiDefault (ansiRequest); + } + + return string.Empty; } #region Not Implemented @@ -1247,7 +1252,7 @@ internal class WindowsDriver : ConsoleDriver { var sb = new StringBuilder (); sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); - WriteAnsi (sb.ToString ()); + WinConsole?.WriteANSI (sb.ToString ()); } if (_cachedCursorVisibility is { }) @@ -1283,8 +1288,7 @@ internal class WindowsDriver : ConsoleDriver { var sb = new StringBuilder (); sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); - - return WriteAnsi (sb.ToString ()); + return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } } @@ -1299,8 +1303,7 @@ internal class WindowsDriver : ConsoleDriver { var sb = new StringBuilder (); sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); - - return WriteAnsi (sb.ToString ()); + return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } //if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) diff --git a/UnitTests/Input/EscSeqReqTests.cs b/UnitTests/Input/EscSeqReqTests.cs index d1499eabd..9f6921796 100644 --- a/UnitTests/Input/EscSeqReqTests.cs +++ b/UnitTests/Input/EscSeqReqTests.cs @@ -8,29 +8,21 @@ public class EscSeqReqTests var escSeqReq = new EscSeqRequests (); escSeqReq.Add (new () { Request = "", Terminator = "t" }); Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); - Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests); - Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); + Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); - Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); - Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding); + Assert.Equal (2, escSeqReq.Statuses.Count); + Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); escSeqReq = new (); escSeqReq.Add (new () { Request = "", Terminator = "t" }); escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); - Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); - Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding); + Assert.Equal (2, escSeqReq.Statuses.Count); + Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); - Assert.Equal (3, escSeqReq.Statuses [^1].NumRequests); - Assert.Equal (3, escSeqReq.Statuses [^1].NumOutstanding); + Assert.Equal (3, escSeqReq.Statuses.Count); + Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); } [Fact] @@ -55,9 +47,7 @@ public class EscSeqReqTests escSeqReq.HasResponse ("t", out seqReqStatus); escSeqReq.Remove (seqReqStatus); Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses [^1].AnsiRequest.Terminator); - Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); - Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); + Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); escSeqReq.HasResponse ("t", out seqReqStatus); escSeqReq.Remove (seqReqStatus); diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 7b6e6538e..ccf8a218c 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -841,7 +841,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); Assert.Single (_escSeqReqProc.Statuses); - Assert.Equal ("t", _escSeqReqProc.Statuses [^1].AnsiRequest.Terminator); + Assert.Equal ("t", _escSeqReqProc.Statuses.ToArray () [^1].AnsiRequest.Terminator); EscSeqUtils.DecodeEscSeq ( _escSeqReqProc, From 93483288c65f261f1bf1e5cd2494d934dfbec49e Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 16 Oct 2024 10:41:02 +0100 Subject: [PATCH 032/151] Rename to WriteAnsiRequest. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 8 ++++---- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 6 +++--- Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs | 6 +++--- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 +- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 942a5f828..4f7b272fb 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -77,7 +77,7 @@ public class AnsiEscapeSequenceRequest driver!.IsSuspendRead = true; // Send the ANSI escape sequence - ansiRequest.Response = driver.WriteAnsi (ansiRequest); + ansiRequest.Response = driver.WriteAnsiRequest (ansiRequest); if (string.IsNullOrEmpty (ansiRequest.Terminator)) { diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 8252ea5e5..fd1e6b965 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -632,10 +632,10 @@ public abstract class ConsoleDriver /// Provide handling for the terminal write ANSI escape sequence request. /// /// The object. - /// - public abstract string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest); + /// The request response. + public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); - internal bool WriteAnsiDefault (string ansi) + internal bool WriteAnsiRequestDefault (string ansi) { try { @@ -654,7 +654,7 @@ public abstract class ConsoleDriver return true; } - internal string ReadAnsiDefault (AnsiEscapeSequenceRequest ansiRequest) + internal string ReadAnsiResponseDefault (AnsiEscapeSequenceRequest ansiRequest) { var response = new StringBuilder (); diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 6d1e3cbc3..75d7ec5d8 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -216,11 +216,11 @@ internal class CursesDriver : ConsoleDriver } /// - public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - if (WriteAnsiDefault (ansiRequest.Request)) + if (WriteAnsiRequestDefault (ansiRequest.Request)) { - return ReadAnsiDefault (ansiRequest); + return ReadAnsiResponseDefault (ansiRequest); } return string.Empty; diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 10a53234d..c5ff53a3b 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -415,11 +415,11 @@ public class FakeDriver : ConsoleDriver public override void StopReportingMouseMoves () { throw new NotImplementedException (); } /// - public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - if (WriteAnsiDefault (ansiRequest.Request)) + if (WriteAnsiRequestDefault (ansiRequest.Request)) { - return ReadAnsiDefault (ansiRequest); + return ReadAnsiResponseDefault (ansiRequest); } return string.Empty; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 2f9f2a031..e29b7a566 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1436,7 +1436,7 @@ internal class NetDriver : ConsoleDriver private readonly CancellationTokenSource _ansiResponseTokenSource = new (); /// - public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 50ac3c956..fb21ad082 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1185,11 +1185,11 @@ internal class WindowsDriver : ConsoleDriver } /// - public override string WriteAnsi (AnsiEscapeSequenceRequest ansiRequest) + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { if (WinConsole?.WriteANSI (ansiRequest.Request) == true) { - return ReadAnsiDefault (ansiRequest); + return ReadAnsiResponseDefault (ansiRequest); } return string.Empty; From 1645d1f95652d07237e7c4eb129885629b08c62e Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 16 Oct 2024 18:50:45 +0100 Subject: [PATCH 033/151] Ensure dequeue on bad requests or without response in NetDriver. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index e29b7a566..7f56a1468 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1435,7 +1435,7 @@ internal class NetDriver : ConsoleDriver internal ManualResetEventSlim _waitAnsiResponse = new (false); private readonly CancellationTokenSource _ansiResponseTokenSource = new (); - /// + /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); @@ -1457,11 +1457,21 @@ internal class NetDriver : ConsoleDriver } finally { + if (_mainLoopDriver is { }) + { + _mainLoopDriver._netEvents._forceRead = false; + } + + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Peek ().AnsiRequest.Response)) + { + // Bad request or no response at all + _mainLoopDriver._netEvents.EscSeqRequests.Statuses.Dequeue (); + } + _waitAnsiResponse.Reset (); } - _mainLoopDriver._netEvents._forceRead = false; - return ansiRequest.Response; } From 34fb5b56ab5be3525a5afb7f7249bc93f39dcad9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 16 Oct 2024 19:01:15 +0100 Subject: [PATCH 034/151] Non-blocking ReadConsoleInput in WindowsDriver. --- .../AnsiEscapeSequenceRequest.cs | 10 ++- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 79 ++++++++++++++++--- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 4f7b272fb..b37e086f2 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -79,11 +79,17 @@ public class AnsiEscapeSequenceRequest // Send the ANSI escape sequence ansiRequest.Response = driver.WriteAnsiRequest (ansiRequest); + if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (EscSeqUtils.KeyEsc)) + { + throw new InvalidOperationException ("Invalid escape character!"); + } + if (string.IsNullOrEmpty (ansiRequest.Terminator)) { - error.AppendLine ("Terminator request is empty."); + throw new InvalidOperationException ("Terminator request is empty."); } - else if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) + + if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) { throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'"); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index fb21ad082..a900e1d88 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -781,6 +781,9 @@ internal class WindowsConsole [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle (nint handle); + [DllImport ("kernel32.dll", SetLastError = true)] + public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead); + [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] public static extern bool ReadConsoleInput ( nint hConsoleInput, @@ -888,14 +891,19 @@ internal class WindowsConsole { const int bufferSize = 1; nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize); + uint numberEventsRead = 0; try { - ReadConsoleInput ( - _inputHandle, - pRecord, - bufferSize, - out uint numberEventsRead); + + if (PeekConsoleInput (_inputHandle, out InputRecord inputRecord, 1, out uint eventsRead) && eventsRead > 0) + { + ReadConsoleInput ( + _inputHandle, + pRecord, + bufferSize, + out numberEventsRead); + } return numberEventsRead == 0 ? null @@ -1187,9 +1195,38 @@ internal class WindowsDriver : ConsoleDriver /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - if (WinConsole?.WriteANSI (ansiRequest.Request) == true) + while (_mainLoopDriver is { } && Console.KeyAvailable) { - return ReadAnsiResponseDefault (ansiRequest); + _mainLoopDriver._waitForProbe.Set (); + _mainLoopDriver._waitForProbe.Reset (); + + _mainLoopDriver._forceRead = true; + } + + if (_mainLoopDriver is { }) + { + _mainLoopDriver._forceRead = false; + } + + _mainLoopDriver._suspendRead = true; + + try + { + if (WinConsole?.WriteANSI (ansiRequest.Request) == true) + { + Thread.Sleep (100); // Allow time for the terminal to respond + + return ReadAnsiResponseDefault (ansiRequest); + } + } + catch (Exception e) + { + return string.Empty; + } + finally + { + _mainLoopDriver._suspendRead = false; + } return string.Empty; @@ -2192,7 +2229,7 @@ internal class WindowsMainLoop : IMainLoopDriver // The records that we keep fetching private readonly Queue _resultQueue = new (); - private readonly ManualResetEventSlim _waitForProbe = new (false); + internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly WindowsConsole _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -2310,6 +2347,9 @@ internal class WindowsMainLoop : IMainLoopDriver _mainLoop = null; } + internal bool _forceRead; + internal bool _suspendRead; + private void WindowsInputHandler () { while (_mainLoop is { }) @@ -2325,6 +2365,7 @@ internal class WindowsMainLoop : IMainLoopDriver { // Wakes the _waitForProbe if it's waiting _waitForProbe.Set (); + return; } finally @@ -2337,9 +2378,27 @@ internal class WindowsMainLoop : IMainLoopDriver } } - if (_resultQueue?.Count == 0) + if (_resultQueue?.Count == 0 || _forceRead) { - _resultQueue.Enqueue (_winConsole.ReadConsoleInput ()); + while (!_inputHandlerTokenSource.IsCancellationRequested) + { + if (!_suspendRead) + { + WindowsConsole.InputRecord[] inpRec = _winConsole.ReadConsoleInput (); + + if (inpRec is { }) + { + _resultQueue!.Enqueue (inpRec); + + break; + } + } + + if (!_forceRead) + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + } } _eventReady.Set (); From 7fa098f0abfe7405b8dce3a3f49fed4a42dcf893 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 18 Oct 2024 13:42:00 +0100 Subject: [PATCH 035/151] Remove unnecessary IsSuspendRead property. --- .../AnsiEscapeSequenceRequest.cs | 3 --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 11 +++-------- .../ConsoleDrivers/CursesDriver/CursesDriver.cs | 7 ------- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 14 +------------- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 7 ------- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 14 +------------- 6 files changed, 5 insertions(+), 51 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index b37e086f2..38d0450d0 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -74,8 +74,6 @@ public class AnsiEscapeSequenceRequest driver.StopReportingMouseMoves (); } - driver!.IsSuspendRead = true; - // Send the ANSI escape sequence ansiRequest.Response = driver.WriteAnsiRequest (ansiRequest); @@ -107,7 +105,6 @@ public class AnsiEscapeSequenceRequest if (savedIsReportingMouseMoves) { - driver!.IsSuspendRead = false; driver.StartReportingMouseMoves (); } } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index fd1e6b965..f3225eceb 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -567,11 +567,6 @@ public abstract class ConsoleDriver /// public abstract bool IsReportingMouseMoves { get; internal set; } - /// - /// Gets whether the terminal is reading input. - /// - public abstract bool IsSuspendRead { get; internal set; } - /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; @@ -619,17 +614,17 @@ public abstract class ConsoleDriver public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); /// - /// Provide handling for the terminal start reporting mouse events. + /// Provide handling for the terminal start reporting mouse events. /// public abstract void StartReportingMouseMoves (); /// - /// Provide handling for the terminal stop reporting mouse events. + /// Provide handling for the terminal stop reporting mouse events. /// public abstract void StopReportingMouseMoves (); /// - /// Provide handling for the terminal write ANSI escape sequence request. + /// Provide handling for the terminal write ANSI escape sequence request. /// /// The object. /// The request response. diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 75d7ec5d8..ff949f1fc 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -180,13 +180,6 @@ internal class CursesDriver : ConsoleDriver public override bool IsReportingMouseMoves { get; internal set; } - /// - public override bool IsSuspendRead - { - get => _isSuspendRead; - internal set => _isSuspendRead = value; - } - public override void StartReportingMouseMoves () { if (!RunningUnitTests) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index c5ff53a3b..2335e9834 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -41,18 +41,7 @@ public class FakeDriver : ConsoleDriver public override bool SupportsTrueColor => false; /// - public override bool IsReportingMouseMoves - { - get => _isReportingMouseMoves; - internal set => _isReportingMouseMoves = value; - } - - /// - public override bool IsSuspendRead - { - get => _isSuspendRead; - internal set => _isSuspendRead = value; - } + public override bool IsReportingMouseMoves { get; internal set; } public FakeDriver () { @@ -351,7 +340,6 @@ public class FakeDriver : ConsoleDriver } private CursorVisibility _savedCursorVisibility; - private bool _isReportingMouseMoves; private bool _isSuspendRead; private void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 7f56a1468..2b35cff1b 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1392,13 +1392,6 @@ internal class NetDriver : ConsoleDriver public override bool IsReportingMouseMoves { get; internal set; } - /// - public override bool IsSuspendRead - { - get => _isSuspendRead; - internal set => _isSuspendRead = value; - } - public override void StartReportingMouseMoves () { if (!RunningUnitTests) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index a900e1d88..7da7b2729 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1052,18 +1052,7 @@ internal class WindowsDriver : ConsoleDriver public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal); /// - public override bool IsReportingMouseMoves - { - get => _isReportingMouseMoves; - internal set => _isReportingMouseMoves = value; - } - - /// - public override bool IsSuspendRead - { - get => _isSuspendRead; - internal set => _isSuspendRead = value; - } + public override bool IsReportingMouseMoves { get; internal set; } public WindowsConsole WinConsole { get; private set; } @@ -1261,7 +1250,6 @@ internal class WindowsDriver : ConsoleDriver #region Cursor Handling private CursorVisibility? _cachedCursorVisibility; - private bool _isReportingMouseMoves; private bool _isSuspendRead; public override void UpdateCursor () From 9759b97ef3be1450d8b4ef84a2507245e8752059 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 18 Oct 2024 20:09:43 +0100 Subject: [PATCH 036/151] Remove unused variable. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 1 - Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs | 1 - Terminal.Gui/ConsoleDrivers/NetDriver.cs | 1 - Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 1 - 4 files changed, 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index ff949f1fc..206b2ba8e 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -18,7 +18,6 @@ internal class CursesDriver : ConsoleDriver private MouseFlags _lastMouseFlags; private UnixMainLoop _mainLoopDriver; private object _processInputToken; - private bool _isSuspendRead; public override int Cols { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 2335e9834..527de31d2 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -340,7 +340,6 @@ public class FakeDriver : ConsoleDriver } private CursorVisibility _savedCursorVisibility; - private bool _isSuspendRead; private void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 2b35cff1b..fe49423cb 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1341,7 +1341,6 @@ internal class NetDriver : ConsoleDriver } private CursorVisibility? _cachedCursorVisibility; - private bool _isSuspendRead; public override void UpdateCursor () { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 7da7b2729..3359e87bc 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1250,7 +1250,6 @@ internal class WindowsDriver : ConsoleDriver #region Cursor Handling private CursorVisibility? _cachedCursorVisibility; - private bool _isSuspendRead; public override void UpdateCursor () { From 1ecff5e5da8d942b1730b7c7587dab5235357a40 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 19 Oct 2024 00:39:57 +0100 Subject: [PATCH 037/151] Fix ansi multi-thread requests handling in the NetDriver. --- .../AnsiEscapeSequenceRequest.cs | 14 +++- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 7 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 77 ++++++++++++++----- 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 38d0450d0..b78e773d0 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -8,6 +8,8 @@ namespace Terminal.Gui; /// public class AnsiEscapeSequenceRequest { + internal readonly object _responseLock = new (); // Per-instance lock + /// /// Request to send e.g. see /// @@ -89,12 +91,14 @@ public class AnsiEscapeSequenceRequest if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) { - throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'"); + char resp = string.IsNullOrEmpty (ansiRequest.Response) ? ' ' : ansiRequest.Response.Last (); + + throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{ansiRequest.Terminator [^1]}'"); } } catch (Exception ex) { - error.AppendLine ($"Error executing ANSI request: {ex.Message}"); + error.AppendLine ($"Error executing ANSI request:\n{ansiRequest.Response}\n{ex.Message}"); } finally { @@ -105,7 +109,7 @@ public class AnsiEscapeSequenceRequest if (savedIsReportingMouseMoves) { - driver.StartReportingMouseMoves (); + driver?.StartReportingMouseMoves (); } } @@ -132,4 +136,8 @@ public class AnsiEscapeSequenceRequest /// different value. /// public string? Value { get; init; } + + internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string response) { ResponseFromInput?.Invoke (ansiRequest, response); } + + internal event EventHandler? ResponseFromInput; } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index 178f8f7a9..8f7a56a19 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -77,7 +77,12 @@ public class EscSeqRequests { lock (Statuses) { - Statuses.Dequeue (); + Statuses.TryDequeue (out var request); + + if (request != seqReqStatus) + { + throw new InvalidOperationException ("Both EscSeqReqStatus objects aren't equals."); + } } } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index fe49423cb..ebf88efb0 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -2,6 +2,7 @@ // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. // +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; @@ -275,17 +276,29 @@ internal class NetEvents : IDisposable } _isEscSeq = true; - newConsoleKeyInfo = consoleKeyInfo; - _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); - if (Console.KeyAvailable) + if (consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) { - continue; - } + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + else + { + newConsoleKeyInfo = consoleKeyInfo; + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + + if (Console.KeyAvailable) + { + continue; + } + + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + } break; } @@ -454,9 +467,11 @@ internal class NetEvents : IDisposable sb.Append (keyChar.KeyChar); } - seqReqStatus.AnsiRequest.Response = sb.ToString (); - - ((NetDriver)_consoleDriver)._waitAnsiResponse.Set (); + lock (seqReqStatus.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = sb.ToString (); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, sb.ToString ()); + } return; } @@ -1424,22 +1439,41 @@ internal class NetDriver : ConsoleDriver } } - internal ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); private readonly CancellationTokenSource _ansiResponseTokenSource = new (); /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); + var response = string.Empty; try { + lock (ansiRequest._responseLock) + { + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == ansiRequest); + + ansiRequest.Response = response = e; + + _waitAnsiResponse.Set (); + }; + + _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); + } + if (!_ansiResponseTokenSource.IsCancellationRequested && Console.KeyAvailable) { _mainLoopDriver._netEvents._forceRead = true; _mainLoopDriver._netEvents._waitForStart.Set (); + if (!_mainLoopDriver._waitForProbe.IsSet) + { + _mainLoopDriver._waitForProbe.Set (); + } + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); } } @@ -1454,17 +1488,20 @@ internal class NetDriver : ConsoleDriver _mainLoopDriver._netEvents._forceRead = false; } - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Peek ().AnsiRequest.Response)) + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) { - // Bad request or no response at all - _mainLoopDriver._netEvents.EscSeqRequests.Statuses.Dequeue (); + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.Response)) + { + // Bad request or no response at all + _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); + } } _waitAnsiResponse.Reset (); } - return ansiRequest.Response; + return response; } private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) @@ -1756,7 +1793,7 @@ internal class NetMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly Queue _resultQueue = new (); - private readonly ManualResetEventSlim _waitForProbe = new (false); + internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private MainLoop _mainLoop; @@ -1856,7 +1893,7 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested) + if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested) { _waitForProbe.Wait (_inputHandlerTokenSource.Token); } From 9a840dd29b8210998fdf01b1a22dbf9eb44dcbd7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 19 Oct 2024 13:55:51 +0100 Subject: [PATCH 038/151] Remove unnecessary WriteAnsiRequestDefault method. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 19 ------------------- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 10 +--------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index f3225eceb..35c90d985 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -630,25 +630,6 @@ public abstract class ConsoleDriver /// The request response. public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); - internal bool WriteAnsiRequestDefault (string ansi) - { - try - { - Console.Out.Write (ansi); - Console.Out.Flush (); // Ensure the request is sent - - // Read the response from stdin (response should come back as input) - Thread.Sleep (100); // Allow time for the terminal to respond - - } - catch (Exception) - { - return false; - } - - return true; - } - internal string ReadAnsiResponseDefault (AnsiEscapeSequenceRequest ansiRequest) { var response = new StringBuilder (); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 527de31d2..66fcb32d1 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -402,15 +402,7 @@ public class FakeDriver : ConsoleDriver public override void StopReportingMouseMoves () { throw new NotImplementedException (); } /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (WriteAnsiRequestDefault (ansiRequest.Request)) - { - return ReadAnsiResponseDefault (ansiRequest); - } - - return string.Empty; - } + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } public void SetBufferSize (int width, int height) { From cc4f7e9a9fdb7038d0ee4eec1c1cd3915ce1c725 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 19 Oct 2024 19:03:31 +0100 Subject: [PATCH 039/151] Simplifying code. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 10 ++++++---- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index ebf88efb0..be1173b13 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1445,6 +1445,11 @@ internal class NetDriver : ConsoleDriver /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { + if (_mainLoopDriver is null) + { + return string.Empty; + } + var response = string.Empty; try @@ -1483,10 +1488,7 @@ internal class NetDriver : ConsoleDriver } finally { - if (_mainLoopDriver is { }) - { - _mainLoopDriver._netEvents._forceRead = false; - } + _mainLoopDriver._netEvents._forceRead = false; if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 3359e87bc..79fcd7d62 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1184,7 +1184,12 @@ internal class WindowsDriver : ConsoleDriver /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - while (_mainLoopDriver is { } && Console.KeyAvailable) + if (_mainLoopDriver is null) + { + return string.Empty; + } + + while (Console.KeyAvailable) { _mainLoopDriver._waitForProbe.Set (); _mainLoopDriver._waitForProbe.Reset (); @@ -1192,11 +1197,7 @@ internal class WindowsDriver : ConsoleDriver _mainLoopDriver._forceRead = true; } - if (_mainLoopDriver is { }) - { - _mainLoopDriver._forceRead = false; - } - + _mainLoopDriver._forceRead = false; _mainLoopDriver._suspendRead = true; try @@ -1208,14 +1209,13 @@ internal class WindowsDriver : ConsoleDriver return ReadAnsiResponseDefault (ansiRequest); } } - catch (Exception e) + catch (Exception) { return string.Empty; } finally { _mainLoopDriver._suspendRead = false; - } return string.Empty; From 542d82dd552a74db023c199f6e9b41dd704c2b96 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 20 Oct 2024 00:33:36 +0100 Subject: [PATCH 040/151] Remove response from error. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index b78e773d0..d7d1e24af 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -98,7 +98,7 @@ public class AnsiEscapeSequenceRequest } catch (Exception ex) { - error.AppendLine ($"Error executing ANSI request:\n{ansiRequest.Response}\n{ex.Message}"); + error.AppendLine ($"Error executing ANSI request:\n{ex.Message}"); } finally { From 974914261039a8483ee1beee7f2ad8d80e9d219c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 20 Oct 2024 01:02:43 +0100 Subject: [PATCH 041/151] Set _forceRead as true before set _waitForProbe. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 79fcd7d62..a25408ac2 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1191,10 +1191,10 @@ internal class WindowsDriver : ConsoleDriver while (Console.KeyAvailable) { + _mainLoopDriver._forceRead = true; + _mainLoopDriver._waitForProbe.Set (); _mainLoopDriver._waitForProbe.Reset (); - - _mainLoopDriver._forceRead = true; } _mainLoopDriver._forceRead = false; From 9eb70236d67ead0feeafe4380ac24e288b590696 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 20 Oct 2024 01:05:53 +0100 Subject: [PATCH 042/151] Improves CursesDriver to allow non-blocking input polling. --- .../CursesDriver/CursesDriver.cs | 32 +++++- .../CursesDriver/UnixMainLoop.cs | 108 ++++++++++++++++-- 2 files changed, 129 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 206b2ba8e..4b90d3596 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -210,12 +210,38 @@ internal class CursesDriver : ConsoleDriver /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - if (WriteAnsiRequestDefault (ansiRequest.Request)) + if (_mainLoopDriver is null) { - return ReadAnsiResponseDefault (ansiRequest); + return string.Empty; } - return string.Empty; + while (Console.KeyAvailable) + { + _mainLoopDriver._forceRead = true; + + _mainLoopDriver._waitForInput.Set (); + _mainLoopDriver._waitForInput.Reset (); + } + + _mainLoopDriver._forceRead = false; + _mainLoopDriver._suspendRead = true; + + try + { + Console.Out.Write (ansiRequest.Request); + Console.Out.Flush (); // Ensure the request is sent + Task.Delay (100).Wait (); // Allow time for the terminal to respond + + return ReadAnsiResponseDefault (ansiRequest); + } + catch (Exception) + { + return string.Empty; + } + finally + { + _mainLoopDriver._suspendRead = false; + } } public override void Suspend () diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 34139815c..c7a9a2612 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -46,6 +46,10 @@ internal class UnixMainLoop : IMainLoopDriver private bool _pollDirty = true; private Pollfd [] _pollMap; private bool _winChanged; + private readonly ManualResetEventSlim _eventReady = new (false); + internal readonly ManualResetEventSlim _waitForInput = new (false); + private readonly CancellationTokenSource _eventReadyTokenSource = new (); + private readonly CancellationTokenSource _inputHandlerTokenSource = new (); public UnixMainLoop (ConsoleDriver consoleDriver = null) { @@ -89,22 +93,99 @@ internal class UnixMainLoop : IMainLoopDriver { throw new NotSupportedException ("libc not found", e); } + + Task.Run (CursesInputHandler, _inputHandlerTokenSource.Token); + } + + internal bool _forceRead; + internal bool _suspendRead; + private int n; + + private void CursesInputHandler () + { + while (_mainLoop is { }) + { + try + { + UpdatePollMap (); + + if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) + { + _waitForInput.Wait (_inputHandlerTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return; + } + finally + { + if (!_inputHandlerTokenSource.IsCancellationRequested) + { + _waitForInput.Reset (); + } + } + + while (!_inputHandlerTokenSource.IsCancellationRequested) + { + if (!_suspendRead) + { + n = poll (_pollMap, (uint)_pollMap.Length, 0); + + if (n == KEY_RESIZE) + { + _winChanged = true; + + break; + } + + if (n > 0) + { + break; + } + } + + if (!_forceRead) + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + } + + _eventReady.Set (); + } } bool IMainLoopDriver.EventsPending () { - UpdatePollMap (); + _waitForInput.Set (); - bool checkTimersResult = _mainLoop.CheckTimersAndIdleHandlers (out int pollTimeout); - - int n = poll (_pollMap, (uint)_pollMap.Length, pollTimeout); - - if (n == KEY_RESIZE) + if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) { - _winChanged = true; + return true; } - return checkTimersResult || n >= KEY_RESIZE; + try + { + if (!_eventReadyTokenSource.IsCancellationRequested) + { + _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return true; + } + finally + { + _eventReady.Reset (); + } + + if (!_eventReadyTokenSource.IsCancellationRequested) + { + return n > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _) || _winChanged; + } + + return true; } void IMainLoopDriver.Iteration () @@ -118,6 +199,8 @@ internal class UnixMainLoop : IMainLoopDriver _cursesDriver.ProcessWinChange (); } + n = 0; + if (_pollMap is null) { return; @@ -148,6 +231,15 @@ internal class UnixMainLoop : IMainLoopDriver { _descriptorWatchers?.Clear (); + _inputHandlerTokenSource?.Cancel (); + _inputHandlerTokenSource?.Dispose (); + + _waitForInput?.Dispose (); + + _eventReadyTokenSource?.Cancel (); + _eventReadyTokenSource?.Dispose (); + _eventReady?.Dispose (); + _mainLoop = null; } From 4090ea4070a306c4fc9a70de58feae6f6ab4bbff Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 20 Oct 2024 01:07:23 +0100 Subject: [PATCH 043/151] Avoids response starting without Esc char on stressing tests. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 35c90d985..59dd20e3e 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -633,14 +633,18 @@ public abstract class ConsoleDriver internal string ReadAnsiResponseDefault (AnsiEscapeSequenceRequest ansiRequest) { var response = new StringBuilder (); + int index = 0; while (Console.KeyAvailable) { // Peek the next key ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console - // Append the current key to the response - response.Append (keyInfo.KeyChar); + if ((index == 0 && keyInfo.KeyChar == EscSeqUtils.KeyEsc) || (index > 0 && keyInfo.KeyChar != EscSeqUtils.KeyEsc)) + { + // Append the current key to the response + response.Append (keyInfo.KeyChar); + } // Read until no key is available if no terminator was specified or // check if the key is terminator (ANSI escape sequence ends) @@ -649,6 +653,8 @@ public abstract class ConsoleDriver // Break out of the loop when terminator is found break; } + + index++; } return response.ToString (); From 5ef34b8e54b875dc93f42f91db94340b171b7951 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 21 Oct 2024 12:25:38 +0100 Subject: [PATCH 044/151] Refactoring code. --- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 74 +++++++++---------- UnitTests/Input/EscSeqUtilsTests.cs | 1 + 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index a8c830f7e..e08fcffff 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -123,19 +123,19 @@ public static class EscSeqUtils public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h"; //private static bool isButtonReleased; - private static bool isButtonClicked; + private static bool _isButtonClicked; - private static bool isButtonDoubleClicked; + private static bool _isButtonDoubleClicked; //private static MouseFlags? lastMouseButtonReleased; // QUESTION: What's the difference between isButtonClicked and isButtonPressed? // Some clarity or comments would be handy, here. // It also seems like some enforcement of valid states might be a good idea. - private static bool isButtonPressed; - private static bool isButtonTripleClicked; + private static bool _isButtonPressed; + private static bool _isButtonTripleClicked; - private static MouseFlags? lastMouseButtonPressed; - private static Point? point; + private static MouseFlags? _lastMouseButtonPressed; + private static Point? _point; /// /// Control sequence for disabling mouse events. @@ -774,32 +774,32 @@ public static class EscSeqUtils mouseFlags = [MouseFlags.AllEvents]; - if (lastMouseButtonPressed != null - && !isButtonPressed + if (_lastMouseButtonPressed != null + && !_isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition) && !buttonState.HasFlag (MouseFlags.Button1Released) && !buttonState.HasFlag (MouseFlags.Button2Released) && !buttonState.HasFlag (MouseFlags.Button3Released) && !buttonState.HasFlag (MouseFlags.Button4Released)) { - lastMouseButtonPressed = null; - isButtonPressed = false; + _lastMouseButtonPressed = null; + _isButtonPressed = false; } - if ((!isButtonClicked - && !isButtonDoubleClicked + if ((!_isButtonClicked + && !_isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) - && lastMouseButtonPressed is null) - || (isButtonPressed && lastMouseButtonPressed is { } && buttonState.HasFlag (MouseFlags.ReportMousePosition))) + && _lastMouseButtonPressed is null) + || (_isButtonPressed && _lastMouseButtonPressed is { } && buttonState.HasFlag (MouseFlags.ReportMousePosition))) { mouseFlags [0] = buttonState; - lastMouseButtonPressed = buttonState; - isButtonPressed = true; + _lastMouseButtonPressed = buttonState; + _isButtonPressed = true; - point = pos; + _point = pos; if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) { @@ -818,7 +818,7 @@ public static class EscSeqUtils } else if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition)) { - point = pos; + _point = pos; // The isButtonPressed must always be true, otherwise we can lose the feature // If mouse flags has ReportMousePosition this feature won't run @@ -826,25 +826,25 @@ public static class EscSeqUtils //isButtonPressed = false; } } - else if (isButtonDoubleClicked + else if (_isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) { mouseFlags [0] = GetButtonTripleClicked (buttonState); - isButtonDoubleClicked = false; - isButtonTripleClicked = true; + _isButtonDoubleClicked = false; + _isButtonTripleClicked = true; } - else if (isButtonClicked + else if (_isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) { mouseFlags [0] = GetButtonDoubleClicked (buttonState); - isButtonClicked = false; - isButtonDoubleClicked = true; + _isButtonClicked = false; + _isButtonDoubleClicked = true; Application.MainLoop.AddIdle ( () => @@ -866,24 +866,24 @@ public static class EscSeqUtils // }); //} - else if (!isButtonClicked - && !isButtonDoubleClicked + else if (!_isButtonClicked + && !_isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released || buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) { mouseFlags [0] = buttonState; - isButtonPressed = false; + _isButtonPressed = false; - if (isButtonTripleClicked) + if (_isButtonTripleClicked) { - isButtonTripleClicked = false; + _isButtonTripleClicked = false; } - else if (pos.X == point?.X && pos.Y == point?.Y) + else if (pos.X == _point?.X && pos.Y == _point?.Y) { mouseFlags.Add (GetButtonClicked (buttonState)); - isButtonClicked = true; + _isButtonClicked = true; Application.MainLoop.AddIdle ( () => @@ -894,7 +894,7 @@ public static class EscSeqUtils }); } - point = pos; + _point = pos; //if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) { // lastMouseButtonReleased = buttonState; @@ -1104,13 +1104,13 @@ public static class EscSeqUtils private static async Task ProcessButtonClickedAsync () { await Task.Delay (300); - isButtonClicked = false; + _isButtonClicked = false; } private static async Task ProcessButtonDoubleClickedAsync () { await Task.Delay (300); - isButtonDoubleClicked = false; + _isButtonDoubleClicked = false; } private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action continuousButtonPressedHandler) @@ -1118,7 +1118,7 @@ public static class EscSeqUtils // PERF: Pause and poll in a hot loop. // This should be replaced with event dispatch and a synchronization primitive such as AutoResetEvent. // Will make a massive difference in responsiveness. - while (isButtonPressed) + while (_isButtonPressed) { await Task.Delay (100); @@ -1129,9 +1129,9 @@ public static class EscSeqUtils break; } - if (isButtonPressed && lastMouseButtonPressed is { } && (mouseFlag & MouseFlags.ReportMousePosition) == 0) + if (_isButtonPressed && _lastMouseButtonPressed is { } && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, point ?? Point.Empty)); + Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, _point ?? Point.Empty)); } } } diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index ccf8a218c..7edb30d5e 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +// ReSharper disable HeuristicUnreachableCode namespace Terminal.Gui.InputTests; From c1063a00fec6612bf400ff2cadfe7484b98ba42e Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 31 Oct 2024 19:10:46 +0000 Subject: [PATCH 045/151] Remove unneeded abstract ConsoleDriver objects. --- .../AnsiEscapeSequenceRequest.cs | 20 ++--------- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 36 ++++++++----------- .../CursesDriver/CursesDriver.cs | 18 ++-------- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 12 ++----- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 23 ++---------- 5 files changed, 24 insertions(+), 85 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index d7d1e24af..76bd24101 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -61,23 +61,14 @@ public class AnsiEscapeSequenceRequest public static bool TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) { var error = new StringBuilder (); - var savedIsReportingMouseMoves = false; - ConsoleDriver? driver = null; var values = new string? [] { null }; try { - driver = Application.Driver; - - savedIsReportingMouseMoves = driver!.IsReportingMouseMoves; - - if (savedIsReportingMouseMoves) - { - driver.StopReportingMouseMoves (); - } + ConsoleDriver? driver = Application.Driver; // Send the ANSI escape sequence - ansiRequest.Response = driver.WriteAnsiRequest (ansiRequest); + ansiRequest.Response = driver?.WriteAnsiRequest (ansiRequest)!; if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (EscSeqUtils.KeyEsc)) { @@ -104,12 +95,7 @@ public class AnsiEscapeSequenceRequest { if (string.IsNullOrEmpty (error.ToString ())) { - (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ()); - } - - if (savedIsReportingMouseMoves) - { - driver?.StartReportingMouseMoves (); + (string? _, string? _, values, string? _) = EscSeqUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ()); } } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 59dd20e3e..2adb68e98 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -144,7 +144,7 @@ public abstract class ConsoleDriver // are correctly combined with the base char, but are ALSO treated as 1 column // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. // - // Until this is addressed (see Issue #), we do our best by + // Until this is addressed (see Issue #), we do our best by // a) Attempting to normalize any CM with the base char to it's left // b) Ignoring any CMs that don't normalize if (Col > 0) @@ -167,7 +167,7 @@ public abstract class ConsoleDriver if (normalized.Length == 1) { // It normalized! We can just set the Cell to the left with the - // normalized codepoint + // normalized codepoint Contents [Row, Col - 1].Rune = (Rune)normalized [0]; // Ignore. Don't move to next column because we're already there @@ -377,7 +377,7 @@ public abstract class ConsoleDriver { Contents [r, c] = new Cell { - Rune = (rune != default ? rune : (Rune)' '), + Rune = rune != default ? rune : (Rune)' ', Attribute = CurrentAttribute, IsDirty = true }; _dirtyLines! [r] = true; @@ -562,11 +562,6 @@ public abstract class ConsoleDriver #region Mouse and Keyboard - /// - /// Gets whether the mouse is reporting move events. - /// - public abstract bool IsReportingMouseMoves { get; internal set; } - /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; @@ -613,16 +608,6 @@ public abstract class ConsoleDriver /// If simulates the Ctrl key being pressed. public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); - /// - /// Provide handling for the terminal start reporting mouse events. - /// - public abstract void StartReportingMouseMoves (); - - /// - /// Provide handling for the terminal stop reporting mouse events. - /// - public abstract void StopReportingMouseMoves (); - /// /// Provide handling for the terminal write ANSI escape sequence request. /// @@ -630,22 +615,29 @@ public abstract class ConsoleDriver /// The request response. public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); + /// + /// Provide proper writing to send escape sequence recognized by the . + /// + /// + public abstract void WriteRaw (string ansi); + internal string ReadAnsiResponseDefault (AnsiEscapeSequenceRequest ansiRequest) { var response = new StringBuilder (); - int index = 0; + var index = 0; while (Console.KeyAvailable) { // Peek the next key ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console - if ((index == 0 && keyInfo.KeyChar == EscSeqUtils.KeyEsc) || (index > 0 && keyInfo.KeyChar != EscSeqUtils.KeyEsc)) + if (index == 0 && keyInfo.KeyChar != EscSeqUtils.KeyEsc) { - // Append the current key to the response - response.Append (keyInfo.KeyChar); + break; } + response.Append (keyInfo.KeyChar); + // Read until no key is available if no terminator was specified or // check if the key is terminator (ANSI escape sequence ends) if (!string.IsNullOrEmpty (ansiRequest.Terminator) && keyInfo.KeyChar == ansiRequest.Terminator [^1]) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 940facee8..5092d266c 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -177,33 +177,19 @@ internal class CursesDriver : ConsoleDriver return true; } - public override bool IsReportingMouseMoves { get; internal set; } - - public override void StartReportingMouseMoves () + public void StartReportingMouseMoves () { if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); - - IsReportingMouseMoves = true; } } - public override void StopReportingMouseMoves () + public void StopReportingMouseMoves () { if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); - - IsReportingMouseMoves = false; - - Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer - - // Flush the input buffer to avoid reading stale input - while (Console.KeyAvailable) - { - Console.ReadKey (true); - } } } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 66fcb32d1..24d39f4c1 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -40,9 +40,6 @@ public class FakeDriver : ConsoleDriver public static Behaviors FakeBehaviors = new (); public override bool SupportsTrueColor => false; - /// - public override bool IsReportingMouseMoves { get; internal set; } - public FakeDriver () { Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH; @@ -395,15 +392,12 @@ public class FakeDriver : ConsoleDriver MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control)); } - /// - public override void StartReportingMouseMoves () { throw new NotImplementedException (); } - - /// - public override void StopReportingMouseMoves () { throw new NotImplementedException (); } - /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } + /// + public override void WriteRaw (string ansi) { throw new NotImplementedException (); } + public void SetBufferSize (int width, int height) { FakeConsole.SetBufferSize (width, height); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 18a2bd3b4..b74a39ae4 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1414,38 +1414,19 @@ internal class NetDriver : ConsoleDriver #region Mouse Handling - public override bool IsReportingMouseMoves { get; internal set; } - - public override void StartReportingMouseMoves () + public void StartReportingMouseMoves () { if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); - - IsReportingMouseMoves = true; } } - public override void StopReportingMouseMoves () + public void StopReportingMouseMoves () { if (!RunningUnitTests) { Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); - - IsReportingMouseMoves = false; - - while (_mainLoopDriver is { _netEvents: { } } && Console.KeyAvailable) - { - _mainLoopDriver._netEvents._waitForStart.Set (); - _mainLoopDriver._netEvents._waitForStart.Reset (); - - _mainLoopDriver._netEvents._forceRead = true; - } - - if (_mainLoopDriver is { _netEvents: { } }) - { - _mainLoopDriver._netEvents._forceRead = false; - } } } From 1b6963f8db20b55c0519217d31bd5de35941f2d9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 31 Oct 2024 20:12:05 +0000 Subject: [PATCH 046/151] Improves a lot of console key mappings. --- .../ConsoleDrivers/ConsoleKeyMapping.cs | 34 ++ .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 16 +- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 512 +++++++++++++++--- UnitTests/Input/EscSeqUtilsTests.cs | 403 +++++++++++++- 4 files changed, 844 insertions(+), 121 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index c3497f3ac..25f87bc56 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -704,6 +704,32 @@ public static class ConsoleKeyMapping return (uint)ConsoleKey.F24; case KeyCode.Tab | KeyCode.ShiftMask: return (uint)ConsoleKey.Tab; + case KeyCode.Space: + return (uint)ConsoleKey.Spacebar; + default: + uint c = (char)keyValue; + + if (c is >= (char)ConsoleKey.A and <= (char)ConsoleKey.Z) + { + return c; + } + + if ((c - 32) is >= (char)ConsoleKey.A and <= (char)ConsoleKey.Z) + { + return (c - 32); + } + + if (Enum.IsDefined (typeof (ConsoleKey), keyValue.ToString ())) + { + return (uint)keyValue; + } + + // DEL + if ((uint)keyValue == 127) + { + return (uint)ConsoleKey.Backspace; + } + break; } isConsoleKey = false; @@ -867,6 +893,14 @@ public static class ConsoleKeyMapping case ConsoleKey.Tab: keyCode = KeyCode.Tab; + break; + case ConsoleKey.Spacebar: + keyCode = KeyCode.Space; + + break; + case ConsoleKey.Backspace: + keyCode = KeyCode.Backspace; + break; default: if ((int)consoleKeyInfo.KeyChar is >= 1 and <= 26) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index 8f7a56a19..4dc98937b 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -29,14 +29,22 @@ public class EscSeqRequests /// instance to list. /// /// The object. - public void Add (AnsiEscapeSequenceRequest ansiRequest) + /// The driver in use. + public void Add (AnsiEscapeSequenceRequest ansiRequest, ConsoleDriver? driver = null) { lock (Statuses) { Statuses.Enqueue (new (ansiRequest)); - Console.Out.Write (ansiRequest.Request); - Console.Out.Flush (); - Thread.Sleep (100); // Allow time for the terminal to respond + + if (driver is null) + { + Console.Out.Write (ansiRequest.Request); + Console.Out.Flush (); + } + else + { + driver.WriteRaw (ansiRequest.Request); + } } } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 8a62c076e..cd7a99139 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -1,4 +1,6 @@ #nullable enable +using Terminal.Gui.ConsoleDrivers; + namespace Terminal.Gui; /// @@ -156,6 +158,11 @@ public static class EscSeqUtils /// public static string CSI_ClearScreen (ClearScreenOptions option) { return $"{CSI}{(int)option}J"; } + /// + /// Specify the incomplete array not yet recognized as valid ANSI escape sequence. + /// + public static ConsoleKeyInfo []? IncompleteCkInfos { get; set; } + /// /// Decodes an ANSI escape sequence. /// @@ -193,10 +200,10 @@ public static class EscSeqUtils char [] kChars = GetKeyCharArray (cki); (c1Control, code, values, terminator) = GetEscapeResult (kChars); isMouse = false; - buttonState = new List { 0 }; + buttonState = [0]; pos = default (Point); seqReqStatus = null; - char keyChar = '\0'; + var keyChar = '\0'; switch (c1Control) { @@ -205,53 +212,112 @@ public static class EscSeqUtils { key = ConsoleKey.Escape; - newConsoleKeyInfo = new ConsoleKeyInfo ( - cki [0].KeyChar, - key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); + newConsoleKeyInfo = new ( + cki [0].KeyChar, + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); } - else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) + else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26 && (uint)cki [1].KeyChar != '\n' && (uint)cki [1].KeyChar != '\r') { key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1); + mod = ConsoleModifiers.Alt | ConsoleModifiers.Control; - newConsoleKeyInfo = new ConsoleKeyInfo ( - cki [1].KeyChar, - key, - false, - true, - true); + newConsoleKeyInfo = new ( + cki [1].KeyChar, + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); } - else + else if (cki [1].KeyChar >= 65 && cki [1].KeyChar <= 90) { - if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) + key = (ConsoleKey)cki [1].KeyChar; + mod = ConsoleModifiers.Shift | ConsoleModifiers.Alt; + + newConsoleKeyInfo = new ( + cki [1].KeyChar, + (ConsoleKey)Math.Min ((uint)key, 255), + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + } + else if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) + { + key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0]; + mod = ConsoleModifiers.Alt; + + newConsoleKeyInfo = new ( + cki [1].KeyChar, + (ConsoleKey)Math.Min ((uint)key, 255), + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + } + else if (cki [1].KeyChar is '\0' or ' ') + { + key = ConsoleKey.Spacebar; + + if (kChars.Length > 1 && kChars [1] == '\0') { - key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0]; + mod = ConsoleModifiers.Alt | ConsoleModifiers.Control; } else { - key = (ConsoleKey)cki [1].KeyChar; + mod = ConsoleModifiers.Shift | ConsoleModifiers.Alt; } - newConsoleKeyInfo = new ConsoleKeyInfo ( - (char)key, - (ConsoleKey)Math.Min ((uint)key, 255), - false, - true, - false); + newConsoleKeyInfo = new ( + cki [1].KeyChar, + (ConsoleKey)Math.Min ((uint)key, 255), + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + } + else if (cki [1].KeyChar is '\n' or '\r') + { + key = ConsoleKey.Enter; + + if (kChars.Length > 1 && kChars [1] == '\n') + { + mod = ConsoleModifiers.Alt | ConsoleModifiers.Control; + } + else + { + mod = ConsoleModifiers.Shift | ConsoleModifiers.Alt; + } + + newConsoleKeyInfo = new ( + cki [1].KeyChar, + (ConsoleKey)Math.Min ((uint)key, 255), + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + } + else + { + key = (ConsoleKey)cki [1].KeyChar; + mod = ConsoleModifiers.Alt; + + newConsoleKeyInfo = new ( + cki [1].KeyChar, + (ConsoleKey)Math.Min ((uint)key, 255), + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); } break; case "SS3": key = GetConsoleKey (terminator [0], values [0], ref mod, ref keyChar); - newConsoleKeyInfo = new ConsoleKeyInfo ( - keyChar, - key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); + newConsoleKeyInfo = new ( + keyChar, + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); break; case "CSI": @@ -279,31 +345,32 @@ public static class EscSeqUtils mod |= GetConsoleModifiers (values [1]); } - newConsoleKeyInfo = new ConsoleKeyInfo ( - keyChar, - key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); + newConsoleKeyInfo = new ( + keyChar, + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); } else { // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/2803 // This is caused by NetDriver depending on Console.KeyAvailable? - throw new InvalidOperationException ("CSI response, but there's no terminator"); + //throw new InvalidOperationException ("CSI response, but there's no terminator"); - //newConsoleKeyInfo = new ConsoleKeyInfo ('\0', - // key, - // (mod & ConsoleModifiers.Shift) != 0, - // (mod & ConsoleModifiers.Alt) != 0, - // (mod & ConsoleModifiers.Control) != 0); + IncompleteCkInfos = cki; } + break; + default: + newConsoleKeyInfo = MapConsoleKeyInfo (cki [0]); + key = newConsoleKeyInfo.Key; + mod = newConsoleKeyInfo.Modifiers; + break; } } - #nullable enable /// /// Gets the c1Control used in the called escape sequence. /// @@ -369,6 +436,7 @@ public static class EscSeqUtils ('B', _) => ConsoleKey.DownArrow, ('C', _) => ConsoleKey.RightArrow, ('D', _) => ConsoleKey.LeftArrow, + ('E', _) => ConsoleKey.Clear, ('F', _) => ConsoleKey.End, ('H', _) => ConsoleKey.Home, ('P', _) => ConsoleKey.F1, @@ -388,18 +456,19 @@ public static class EscSeqUtils ('~', "21") => ConsoleKey.F10, ('~', "23") => ConsoleKey.F11, ('~', "24") => ConsoleKey.F12, - ('l', _) => ConsoleKey.Add, - ('m', _) => ConsoleKey.Subtract, - ('p', _) => ConsoleKey.Insert, - ('q', _) => ConsoleKey.End, - ('r', _) => ConsoleKey.DownArrow, - ('s', _) => ConsoleKey.PageDown, - ('t', _) => ConsoleKey.LeftArrow, - ('u', _) => ConsoleKey.Clear, - ('v', _) => ConsoleKey.RightArrow, - ('w', _) => ConsoleKey.Home, - ('x', _) => ConsoleKey.UpArrow, - ('y', _) => ConsoleKey.PageUp, + // These terminators are used by macOS on a numeric keypad without keys modifiers + ('l', null) => ConsoleKey.Add, + ('m', null) => ConsoleKey.Subtract, + ('p', null) => ConsoleKey.Insert, + ('q', null) => ConsoleKey.End, + ('r', null) => ConsoleKey.DownArrow, + ('s', null) => ConsoleKey.PageDown, + ('t', null) => ConsoleKey.LeftArrow, + ('u', null) => ConsoleKey.Clear, + ('v', null) => ConsoleKey.RightArrow, + ('w', null) => ConsoleKey.Home, + ('x', null) => ConsoleKey.UpArrow, + ('y', null) => ConsoleKey.PageUp, (_, _) => 0 }; } @@ -434,7 +503,7 @@ public static class EscSeqUtils /// public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar) { - if (kChar is null || kChar.Length == 0) + if (kChar is null || kChar.Length == 0 || (kChar.Length == 1 && kChar [0] != KeyEsc)) { return (null, null, null, null); } @@ -803,7 +872,7 @@ public static class EscSeqUtils if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.AddIdle ( + Application.MainLoop?.AddIdle ( () => { // INTENT: What's this trying to do? @@ -846,7 +915,7 @@ public static class EscSeqUtils _isButtonClicked = false; _isButtonDoubleClicked = true; - Application.MainLoop.AddIdle ( + Application.MainLoop?.AddIdle ( () => { Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); @@ -885,7 +954,7 @@ public static class EscSeqUtils mouseFlags.Add (GetButtonClicked (buttonState)); _isButtonClicked = true; - Application.MainLoop.AddIdle ( + Application.MainLoop?.AddIdle ( () => { Task.Run (async () => await ProcessButtonClickedAsync ()); @@ -952,7 +1021,7 @@ public static class EscSeqUtils public static ConsoleKeyInfo MapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) { ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo; - ConsoleKey key; + ConsoleKey key = ConsoleKey.None; char keyChar = consoleKeyInfo.KeyChar; switch ((uint)keyChar) @@ -960,56 +1029,165 @@ public static class EscSeqUtils case 0: if (consoleKeyInfo.Key == (ConsoleKey)64) { // Ctrl+Space in Windows. - newConsoleKeyInfo = new ConsoleKeyInfo ( - ' ', - ConsoleKey.Spacebar, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + ConsoleKey.Spacebar, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + } + else if (consoleKeyInfo.Key == ConsoleKey.None) + { + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + ConsoleKey.Spacebar, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + true); } break; - case uint n when n > 0 && n <= KeyEsc: - if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') + case uint n when n is > 0 and <= KeyEsc: + if (consoleKeyInfo is { Key: 0, KeyChar: '\t' }) + { + key = ConsoleKey.Tab; + + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + } + else if (consoleKeyInfo is { Key: 0, KeyChar: '\r' }) { key = ConsoleKey.Enter; - newConsoleKeyInfo = new ConsoleKeyInfo ( - consoleKeyInfo.KeyChar, - key, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + } + else if (consoleKeyInfo is { Key: 0, KeyChar: '\n' }) + { + key = ConsoleKey.Enter; + + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + true); } else if (consoleKeyInfo.Key == 0) { key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1); - newConsoleKeyInfo = new ConsoleKeyInfo ( - (char)key, - key, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - true); + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + true); } break; case 127: // DEL - newConsoleKeyInfo = new ConsoleKeyInfo ( - consoleKeyInfo.KeyChar, - ConsoleKey.Backspace, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + key = ConsoleKey.Backspace; + + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); break; default: - newConsoleKeyInfo = consoleKeyInfo; + uint ck = ConsoleKeyMapping.MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey); + + if (isConsoleKey) + { + key = (ConsoleKey)ck; + } + + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + GetShiftMod (consoleKeyInfo.Modifiers), + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); break; } return newConsoleKeyInfo; + + bool GetShiftMod (ConsoleModifiers modifiers) + { + if (consoleKeyInfo.KeyChar is >= (char)ConsoleKey.A and <= (char)ConsoleKey.Z && modifiers == ConsoleModifiers.None) + { + return true; + } + + return (modifiers & ConsoleModifiers.Shift) != 0; + } + } + + private static MouseFlags _lastMouseFlags; + + /// + /// Provides a handler to be invoked when mouse continuous button pressed is processed. + /// + public static event EventHandler ContinuousButtonPressed; + + /// + /// Provides a default mouse event handler that can be used by any driver. + /// + /// The mouse flags event. + /// The mouse position. + public static void ProcessMouseEvent (MouseFlags mouseFlag, Point pos) + { + bool WasButtonReleased (MouseFlags flag) + { + return flag.HasFlag (MouseFlags.Button1Released) + || flag.HasFlag (MouseFlags.Button2Released) + || flag.HasFlag (MouseFlags.Button3Released) + || flag.HasFlag (MouseFlags.Button4Released); + } + + bool IsButtonNotPressed (MouseFlags flag) + { + return !flag.HasFlag (MouseFlags.Button1Pressed) + && !flag.HasFlag (MouseFlags.Button2Pressed) + && !flag.HasFlag (MouseFlags.Button3Pressed) + && !flag.HasFlag (MouseFlags.Button4Pressed); + } + + bool IsButtonClickedOrDoubleClicked (MouseFlags flag) + { + return flag.HasFlag (MouseFlags.Button1Clicked) + || flag.HasFlag (MouseFlags.Button2Clicked) + || flag.HasFlag (MouseFlags.Button3Clicked) + || flag.HasFlag (MouseFlags.Button4Clicked) + || flag.HasFlag (MouseFlags.Button1DoubleClicked) + || flag.HasFlag (MouseFlags.Button2DoubleClicked) + || flag.HasFlag (MouseFlags.Button3DoubleClicked) + || flag.HasFlag (MouseFlags.Button4DoubleClicked); + } + + if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) + { + return; + } + + _lastMouseFlags = mouseFlag; + + var me = new MouseEventArgs { Flags = mouseFlag, Position = pos }; + + ContinuousButtonPressed?.Invoke ((mouseFlag, pos), me); } /// @@ -1021,7 +1199,61 @@ public static class EscSeqUtils public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki) { Array.Resize (ref cki, cki is null ? 1 : cki.Length + 1); - cki [cki.Length - 1] = consoleKeyInfo; + cki [^1] = consoleKeyInfo; + + return cki; + } + + /// + /// Insert a array into the another array at the specified + /// index. + /// + /// The array to insert. + /// The array where will be added the array. + /// The start index to insert the array, default is 0. + /// The array with another array inserted. + public static ConsoleKeyInfo [] InsertArray ([CanBeNull] ConsoleKeyInfo [] toInsert, ConsoleKeyInfo [] cki, int index = 0) + { + if (toInsert is null) + { + return cki; + } + + if (cki is null) + { + return toInsert; + } + + if (index < 0) + { + index = 0; + } + + ConsoleKeyInfo [] backupCki = cki.Clone () as ConsoleKeyInfo []; + + Array.Resize (ref cki, cki.Length + toInsert.Length); + + for (var i = 0; i < cki.Length; i++) + { + if (i == index) + { + for (var j = 0; j < toInsert.Length; j++) + { + cki [i] = toInsert [j]; + i++; + } + + for (int k = index; k < backupCki!.Length; k++) + { + cki [i] = backupCki [k]; + i++; + } + } + else + { + cki [i] = backupCki! [i]; + } + } return cki; } @@ -1156,6 +1388,108 @@ public static class EscSeqUtils return mouseFlag; } + /// + /// Split a raw string into a list of string with the correct ansi escape sequence. + /// + /// The raw string containing one or many ansi escape sequence. + /// A list with a valid ansi escape sequence. + public static List SplitEscapeRawString (string rawData) + { + List splitList = []; + var isEscSeq = false; + var split = string.Empty; + char previousChar = '\0'; + + for (var i = 0; i < rawData.Length; i++) + { + char c = rawData [i]; + + if (c == '\u001B') + { + isEscSeq = true; + + split = AddAndClearSplit (); + + split += c.ToString (); + } + else if (!isEscSeq && c >= Key.Space) + { + split = AddAndClearSplit (); + splitList.Add (c.ToString ()); + } + else if (previousChar != '\u001B' && c < Key.Space)// uint n when n is > 0 and <= KeyEsc + { + isEscSeq = false; + split = AddAndClearSplit (); + splitList.Add (c.ToString ()); + } + else + { + split += c.ToString (); + } + + if (!string.IsNullOrEmpty (split) && i == rawData.Length - 1) + { + splitList.Add (split); + } + + previousChar = c; + } + + return splitList; + + string AddAndClearSplit () + { + if (!string.IsNullOrEmpty (split)) + { + splitList.Add (split); + split = string.Empty; + } + + return split; + } + } + + /// + /// Convert a array to string. + /// + /// + /// The string representing the array. + public static string ToString (ConsoleKeyInfo [] consoleKeyInfos) + { + StringBuilder sb = new (); + + foreach (ConsoleKeyInfo keyChar in consoleKeyInfos) + { + sb.Append (keyChar.KeyChar); + } + + return sb.ToString (); + } + + /// + /// Convert a string to array. + /// + /// + /// The representing the string. + public static ConsoleKeyInfo [] ToConsoleKeyInfoArray (string ansi) + { + if (ansi is null) + { + return null; + } + + ConsoleKeyInfo [] cki = new ConsoleKeyInfo [ansi.Length]; + + for (var i = 0; i < ansi.Length; i++) + { + char c = ansi [i]; + cki [i] = new (c, 0, false, false, false); + } + + return cki; + } + #region Cursor //ESC [ M - RI Reverse Index – Performs the reverse operation of \n, moves cursor up one line, maintains horizontal position, scrolls buffer if necessary* diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 7edb30d5e..5991cd26c 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -23,7 +23,7 @@ public class EscSeqUtilsTests [Fact] [AutoInitShutdown] - public void DecodeEscSeq_Tests () + public void DecodeEscSeq_Multiple_Tests () { // ESC _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false) }; @@ -83,7 +83,7 @@ public class EscSeqUtilsTests Assert.Null (_escSeqReqProc); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.R, _key); - Assert.Equal (0, (int)_mod); + Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); Assert.Equal ("ESC", _c1Control); Assert.Null (_code); Assert.Null (_values); @@ -97,7 +97,7 @@ public class EscSeqUtilsTests ClearAll (); _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false), new ('r', 0, false, false, false) }; - expectedCki = new ('R', ConsoleKey.R, false, true, false); + expectedCki = new ('r', ConsoleKey.R, false, true, false); EscSeqUtils.DecodeEscSeq ( _escSeqReqProc, @@ -118,7 +118,7 @@ public class EscSeqUtilsTests Assert.Null (_escSeqReqProc); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.R, _key); - Assert.Equal (0, (int)_mod); + Assert.Equal (ConsoleModifiers.Alt, _mod); Assert.Equal ("ESC", _c1Control); Assert.Null (_code); Assert.Null (_values); @@ -877,6 +877,260 @@ public class EscSeqUtilsTests Assert.NotNull (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); + + ClearAll (); + } + + [Theory] + [InlineData ('A', ConsoleKey.A, true, true, false, "ESC", '\u001b', 'A')] + [InlineData ('a', ConsoleKey.A, false, true, false, "ESC", '\u001b', 'a')] + [InlineData ('\0', ConsoleKey.Spacebar, false, true, true, "ESC", '\u001b', '\0')] + [InlineData (' ', ConsoleKey.Spacebar, true, true, false, "ESC", '\u001b', ' ')] + [InlineData ('\n', ConsoleKey.Enter, false, true, true, "ESC", '\u001b', '\n')] + [InlineData ('\r', ConsoleKey.Enter, true, true, false, "ESC", '\u001b', '\r')] + public void DecodeEscSeq_More_Multiple_Tests ( + char keyChar, + ConsoleKey consoleKey, + bool shift, + bool alt, + bool control, + string c1Control, + params char [] kChars + ) + { + _cki = new ConsoleKeyInfo [kChars.Length]; + + for (var i = 0; i < kChars.Length; i++) + { + char kChar = kChars [i]; + _cki [i] = new (kChar, 0, false, false, false); + } + + var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); + + EscSeqUtils.DecodeEscSeq ( + _escSeqReqProc, + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Null (_escSeqReqProc); + Assert.Equal (expectedCki, _newConsoleKeyInfo); + Assert.Equal (consoleKey, _key); + + ConsoleModifiers mods = new (); + + if (shift) + { + mods = ConsoleModifiers.Shift; + } + + if (alt) + { + mods |= ConsoleModifiers.Alt; + } + + if (control) + { + mods |= ConsoleModifiers.Control; + } + + Assert.Equal (mods, _mod); + Assert.Equal (c1Control, _c1Control); + Assert.Null (_code); + Assert.Null (_values); + Assert.Equal (keyChar.ToString (), _terminating); + Assert.False (_isKeyMouse); + Assert.Equal (new () { 0 }, _mouseFlags); + Assert.Equal (Point.Empty, _pos); + Assert.Null (_seqReqStatus); + Assert.Equal (0, (int)_arg1); + Assert.Equal (Point.Empty, _arg2); + + ClearAll (); + } + + [Fact] + public void DecodeEscSeq_IncompleteCKInfos () + { + // This is simulated response from a CSI_ReportTerminalSizeInChars + _cki = + [ + new ('\u001b', 0, false, false, false), + new ('[', 0, false, false, false), + new ('8', 0, false, false, false), + new (';', 0, false, false, false), + new ('1', 0, false, false, false), + ]; + + ConsoleKeyInfo expectedCki = default; + + EscSeqUtils.DecodeEscSeq ( + _escSeqReqProc, + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Null (_escSeqReqProc); + Assert.Equal (expectedCki, _newConsoleKeyInfo); + Assert.Equal (ConsoleKey.None, _key); + Assert.Equal (ConsoleModifiers.None, _mod); + Assert.Equal ("CSI", _c1Control); + Assert.Null (_code); + Assert.Equal (2, _values.Length); + Assert.Equal ("", _terminating); + Assert.False (_isKeyMouse); + Assert.Equal ([0], _mouseFlags); + Assert.Equal (Point.Empty, _pos); + Assert.Null (_seqReqStatus); + Assert.Equal (0, (int)_arg1); + Assert.Equal (Point.Empty, _arg2); + Assert.Equal (_cki, EscSeqUtils.IncompleteCkInfos); + + _cki = EscSeqUtils.InsertArray ( + EscSeqUtils.IncompleteCkInfos, + [ + new ('0', 0, false, false, false), + new (';', 0, false, false, false), + new ('2', 0, false, false, false), + new ('0', 0, false, false, false), + new ('t', 0, false, false, false) + ]); + + expectedCki = default; + + EscSeqUtils.DecodeEscSeq ( + _escSeqReqProc, + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Null (_escSeqReqProc); + Assert.Equal (expectedCki, _newConsoleKeyInfo); + Assert.Equal (ConsoleKey.None, _key); + + Assert.Equal (ConsoleModifiers.None, _mod); + Assert.Equal ("CSI", _c1Control); + Assert.Null (_code); + Assert.Equal (3, _values.Length); + Assert.Equal ("t", _terminating); + Assert.False (_isKeyMouse); + Assert.Equal ([0], _mouseFlags); + Assert.Equal (Point.Empty, _pos); + Assert.Null (_seqReqStatus); + Assert.Equal (0, (int)_arg1); + Assert.Equal (Point.Empty, _arg2); + Assert.NotEqual (_cki, EscSeqUtils.IncompleteCkInfos); + Assert.Contains (EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos), EscSeqUtils.ToString (_cki)); + + ClearAll (); + } + + [Theory] + [InlineData ('\u001B', ConsoleKey.Escape, false, false, false)] + [InlineData ('\r', ConsoleKey.Enter, false, false, false)] + [InlineData ('1', ConsoleKey.D1, false, false, false)] + [InlineData ('!', ConsoleKey.None, false, false, false)] + [InlineData ('a', ConsoleKey.A, false, false, false)] + [InlineData ('A', ConsoleKey.A, true, false, false)] + [InlineData ('\u0001', ConsoleKey.A, false, false, true)] + [InlineData ('\0', ConsoleKey.Spacebar, false, false, true)] + [InlineData ('\n', ConsoleKey.Enter, false, false, true)] + [InlineData ('\t', ConsoleKey.Tab, false, false, false)] + public void DecodeEscSeq_Single_Tests (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) + { + _cki = [new (keyChar, 0, false, false, false)]; + var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); + + EscSeqUtils.DecodeEscSeq ( + _escSeqReqProc, + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Null (_escSeqReqProc); + Assert.Equal (expectedCki, _newConsoleKeyInfo); + Assert.Equal (consoleKey, _key); + + ConsoleModifiers mods = new (); + + if (shift) + { + mods = ConsoleModifiers.Shift; + } + + if (alt) + { + mods |= ConsoleModifiers.Alt; + } + + if (control) + { + mods |= ConsoleModifiers.Control; + } + + Assert.Equal (mods, _mod); + + if (keyChar == '\u001B') + { + Assert.Equal ("ESC", _c1Control); + } + else + { + Assert.Null (_c1Control); + } + + Assert.Null (_code); + Assert.Null (_values); + Assert.Null (_terminating); + Assert.False (_isKeyMouse); + Assert.Equal (new () { 0 }, _mouseFlags); + Assert.Equal (Point.Empty, _pos); + Assert.Null (_seqReqStatus); + Assert.Equal (0, (int)_arg1); + Assert.Equal (Point.Empty, _arg2); + + ClearAll (); } [Fact] @@ -920,39 +1174,39 @@ public class EscSeqUtilsTests public void GetConsoleInputKey_ConsoleKeyInfo () { var cki = new ConsoleKeyInfo ('r', 0, false, false, false); - var expectedCki = new ConsoleKeyInfo ('r', 0, false, false, false); + var expectedCki = new ConsoleKeyInfo ('r', ConsoleKey.R, false, false, false); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, false, false); - expectedCki = new ('r', 0, true, false, false); + expectedCki = new ('r', ConsoleKey.R, true, false, false); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, true, false); - expectedCki = new ('r', 0, false, true, false); + expectedCki = new ('r', ConsoleKey.R, false, true, false); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, false, true); - expectedCki = new ('r', 0, false, false, true); + expectedCki = new ('r', ConsoleKey.R, false, false, true); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, true, false); - expectedCki = new ('r', 0, true, true, false); + expectedCki = new ('r', ConsoleKey.R, true, true, false); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, true, true); - expectedCki = new ('r', 0, false, true, true); + expectedCki = new ('r', ConsoleKey.R, false, true, true); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, true, true); - expectedCki = new ('r', 0, true, true, true); + expectedCki = new ('r', ConsoleKey.R, true, true, true); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\u0012', 0, false, false, false); - expectedCki = new ('R', ConsoleKey.R, false, false, true); + expectedCki = new ('\u0012', ConsoleKey.R, false, false, true); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\0', (ConsoleKey)64, false, false, true); - expectedCki = new (' ', ConsoleKey.Spacebar, false, false, true); + expectedCki = new ('\0', ConsoleKey.Spacebar, false, false, true); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\r', 0, false, false, false); @@ -964,7 +1218,7 @@ public class EscSeqUtilsTests Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('R', 0, false, false, false); - expectedCki = new ('R', 0, false, false, false); + expectedCki = new ('R', ConsoleKey.R, true, false, false); Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); } @@ -999,18 +1253,19 @@ public class EscSeqUtilsTests Assert.Equal (ConsoleKey.F11, EscSeqUtils.GetConsoleKey ('~', "23", ref mod, ref keyChar)); Assert.Equal (ConsoleKey.F12, EscSeqUtils.GetConsoleKey ('~', "24", ref mod, ref keyChar)); Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('~', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Add, EscSeqUtils.GetConsoleKey ('l', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Subtract, EscSeqUtils.GetConsoleKey ('m', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('p', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('q', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('r', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('s', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('t', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Clear, EscSeqUtils.GetConsoleKey ('u', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('v', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('w', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('x', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('y', "", ref mod, ref keyChar)); + // These terminators are used by macOS on a numeric keypad without keys modifiers + Assert.Equal (ConsoleKey.Add, EscSeqUtils.GetConsoleKey ('l', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Subtract, EscSeqUtils.GetConsoleKey ('m', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('p', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('q', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('r', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('s', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('t', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Clear, EscSeqUtils.GetConsoleKey ('u', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('v', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('w', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('x', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('y', null, ref mod, ref keyChar)); } [Fact] @@ -1031,9 +1286,9 @@ public class EscSeqUtilsTests } [Fact] - public void GetEscapeResult_Tests () + public void GetEscapeResult_Multiple_Tests () { - char [] kChars = { '\u001b', '[', '5', ';', '1', '0', 'r' }; + char [] kChars = ['\u001b', '[', '5', ';', '1', '0', 'r']; (_c1Control, _code, _values, _terminating) = EscSeqUtils.GetEscapeResult (kChars); Assert.Equal ("CSI", _c1Control); Assert.Null (_code); @@ -1043,6 +1298,31 @@ public class EscSeqUtilsTests Assert.Equal ("r", _terminating); } + [Theory] + [InlineData ('\u001B')] + [InlineData (['\r'])] + [InlineData (['1'])] + [InlineData (['!'])] + [InlineData (['a'])] + [InlineData (['A'])] + public void GetEscapeResult_Single_Tests (params char [] kChars) + { + (_c1Control, _code, _values, _terminating) = EscSeqUtils.GetEscapeResult (kChars); + + if (kChars [0] == '\u001B') + { + Assert.Equal ("ESC", _c1Control); + } + else + { + Assert.Null (_c1Control); + } + + Assert.Null (_code); + Assert.Null (_values); + Assert.Null (_terminating); + } + [Fact] public void GetKeyCharArray_Tests () { @@ -1160,6 +1440,73 @@ public class EscSeqUtilsTests Assert.Equal (cki, expectedCkInfos [0]); } + [Fact] + public void SplitEscapeRawString_Multiple_Tests () + { + string rawData = + "\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\r"; + + List splitList = EscSeqUtils.SplitEscapeRawString (rawData); + Assert.Equal (18, splitList.Count); + Assert.Equal ("\r", splitList [0]); + Assert.Equal ("\u001b[<35;50;1m", splitList [1]); + Assert.Equal ("\u001b[<35;49;1m", splitList [2]); + Assert.Equal ("\u001b[<35;47;1m", splitList [3]); + Assert.Equal ("\u001b[<35;46;1m", splitList [4]); + Assert.Equal ("\u001b[<35;45;2m", splitList [5]); + Assert.Equal ("\u001b[<35;44;2m", splitList [6]); + Assert.Equal ("\u001b[<35;43;3m", splitList [7]); + Assert.Equal ("\u001b[<35;42;3m", splitList [8]); + Assert.Equal ("\u001b[<35;41;4m", splitList [9]); + Assert.Equal ("\u001b[<35;40;5m", splitList [10]); + Assert.Equal ("\u001b[<35;39;6m", splitList [11]); + Assert.Equal ("\u001b[<35;49;1m", splitList [12]); + Assert.Equal ("\u001b[<35;48;2m", splitList [13]); + Assert.Equal ("\u001b[<0;33;6M", splitList [14]); + Assert.Equal ("\u001b[<0;33;6m", splitList [15]); + Assert.Equal ("\u001bOC", splitList [16]); + Assert.Equal ("\r", splitList [^1]); + } + + [Theory] + [InlineData ("[<35;50;1m")] + [InlineData ("\r")] + [InlineData ("1")] + [InlineData ("!")] + [InlineData ("a")] + [InlineData ("A")] + public void SplitEscapeRawString_Single_Tests (string rawData) + { + List splitList = EscSeqUtils.SplitEscapeRawString (rawData); + Assert.Single (splitList); + Assert.Equal (rawData, splitList [0]); + } + + [Theory] + [InlineData (null, null, null, null)] + [InlineData ("\u001b[8;1", null, null, "\u001b[8;1")] + [InlineData (null, "\u001b[8;1", 5, "\u001b[8;1")] + [InlineData ("\u001b[8;1", null, 5, "\u001b[8;1")] + [InlineData ("\u001b[8;1", "0;20t", -1, "\u001b[8;10;20t")] + [InlineData ("\u001b[8;1", "0;20t", 0, "\u001b[8;10;20t")] + [InlineData ("0;20t", "\u001b[8;1", 5, "\u001b[8;10;20t")] + [InlineData ("0;20t", "\u001b[8;1", 3, "\u001b[80;20t;1")] + public void InsertArray_Tests (string toInsert, string current, int? index, string expected) + { + ConsoleKeyInfo [] toIns = EscSeqUtils.ToConsoleKeyInfoArray (toInsert); + ConsoleKeyInfo [] cki = EscSeqUtils.ToConsoleKeyInfoArray (current); + ConsoleKeyInfo [] result = EscSeqUtils.ToConsoleKeyInfoArray (expected); + + if (index is null) + { + Assert.Equal (result, EscSeqUtils.InsertArray (toIns, cki)); + } + else + { + Assert.Equal (result, EscSeqUtils.InsertArray (toIns, cki, (int)index)); + } + } + private void ClearAll () { _escSeqReqProc = default (EscSeqRequests); From ba607f13804e1512a8e05289abae1322af5f06e0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 31 Oct 2024 20:14:22 +0000 Subject: [PATCH 047/151] Commenting GetIsKeyCodeAtoZ contradiction debug check. --- Terminal.Gui/Input/Key.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index 11f8d938e..d69552aa6 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -208,13 +208,13 @@ public class Key : EventArgs, IEquatable get => _keyCode; init { -#if DEBUG - if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0) - { - throw new ArgumentException ($"Invalid KeyCode: {value} is invalid.", nameof (value)); - } +//#if DEBUG +// if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0) +// { +// throw new ArgumentException ($"Invalid KeyCode: {value} is invalid.", nameof (value)); +// } -#endif +//#endif _keyCode = value; } } From ee8d0404075aec3529e9c90bc27effc240aefa84 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 31 Oct 2024 20:30:22 +0000 Subject: [PATCH 048/151] Fix NetDriver and WindowsDriver. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 65 +++++++++++++++----- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 27 +++----- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index b74a39ae4..df57c6b2f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -198,7 +198,7 @@ internal class NetEvents : IDisposable return null; } - private static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true) + private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true) { // if there is a key available, return it without waiting // (or dispatching work to the thread queue) @@ -215,6 +215,30 @@ internal class NetEvents : IDisposable { return Console.ReadKey (intercept); } + + if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + { + if (_retries > 1) + { + EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); + + lock (seqReqStatus.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } } cancellationToken.ThrowIfCancellationRequested (); @@ -223,6 +247,7 @@ internal class NetEvents : IDisposable } internal bool _forceRead; + private int _retries; private void ProcessInputQueue () { @@ -230,7 +255,10 @@ internal class NetEvents : IDisposable { try { - _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); + if (!_forceRead) + { + _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); + } } catch (OperationCanceledException) { @@ -258,6 +286,11 @@ internal class NetEvents : IDisposable return; } + if (EscSeqUtils.IncompleteCkInfos is { }) + { + EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki); + } + if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) { @@ -277,7 +310,7 @@ internal class NetEvents : IDisposable _isEscSeq = true; - if (consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + if (_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) { ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; @@ -322,6 +355,11 @@ internal class NetEvents : IDisposable ProcessMapConsoleKeyInfo (consoleKeyInfo); + if (_retries > 0) + { + _retries = 0; + } + break; } } @@ -460,17 +498,13 @@ internal class NetEvents : IDisposable if (seqReqStatus is { }) { //HandleRequestResponseEvent (c1Control, code, values, terminating); - StringBuilder sb = new (); - foreach (ConsoleKeyInfo keyChar in cki) - { - sb.Append (keyChar.KeyChar); - } + var ckiString = EscSeqUtils.ToString (cki); lock (seqReqStatus.AnsiRequest._responseLock) { - seqReqStatus.AnsiRequest.Response = sb.ToString (); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, sb.ToString ()); + seqReqStatus.AnsiRequest.Response = ckiString; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString); } return; @@ -1165,7 +1199,7 @@ internal class NetDriver : ConsoleDriver return new MainLoop (_mainLoopDriver); } - + private void ProcessInput (InputResult inputEvent) { switch (inputEvent.EventType) @@ -1457,12 +1491,12 @@ internal class NetDriver : ConsoleDriver }; _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); + + _mainLoopDriver._netEvents._forceRead = true; } - if (!_ansiResponseTokenSource.IsCancellationRequested && Console.KeyAvailable) + if (!_ansiResponseTokenSource.IsCancellationRequested) { - _mainLoopDriver._netEvents._forceRead = true; - _mainLoopDriver._netEvents._waitForStart.Set (); if (!_mainLoopDriver._waitForProbe.IsSet) @@ -1497,6 +1531,9 @@ internal class NetDriver : ConsoleDriver return response; } + /// + public override void WriteRaw (string ansi) { throw new NotImplementedException (); } + private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) { //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 71bdb2c47..57848301a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -18,9 +18,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text; using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; -using static Terminal.Gui.SpinnerStyle; namespace Terminal.Gui; @@ -1066,9 +1064,6 @@ internal class WindowsDriver : ConsoleDriver public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal); - /// - public override bool IsReportingMouseMoves { get; internal set; } - public WindowsConsole WinConsole { get; private set; } public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) @@ -1196,7 +1191,7 @@ internal class WindowsDriver : ConsoleDriver } } - /// + /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { if (_mainLoopDriver is null) @@ -1217,12 +1212,11 @@ internal class WindowsDriver : ConsoleDriver try { - if (WinConsole?.WriteANSI (ansiRequest.Request) == true) - { - Thread.Sleep (100); // Allow time for the terminal to respond + WriteRaw (ansiRequest.Request); - return ReadAnsiResponseDefault (ansiRequest); - } + Thread.Sleep (100); // Allow time for the terminal to respond + + return ReadAnsiResponseDefault (ansiRequest); } catch (Exception) { @@ -1232,18 +1226,17 @@ internal class WindowsDriver : ConsoleDriver { _mainLoopDriver._suspendRead = false; } - - return string.Empty; } #region Not Implemented - public override void StartReportingMouseMoves () { throw new NotImplementedException (); } - - public override void StopReportingMouseMoves () { throw new NotImplementedException (); } - public override void Suspend () { throw new NotImplementedException (); } + public override void WriteRaw (string ansi) + { + WinConsole?.WriteANSI (ansi); + } + #endregion public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) From 8ab7ec5096313d78eaaa71133e8b56fb16d2c755 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 31 Oct 2024 21:03:37 +0000 Subject: [PATCH 049/151] Fix for WindowsDriver. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 2adb68e98..e3cdb7252 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -633,7 +633,7 @@ public abstract class ConsoleDriver if (index == 0 && keyInfo.KeyChar != EscSeqUtils.KeyEsc) { - break; + continue; } response.Append (keyInfo.KeyChar); From de45b4bf397574e8f6f71efa673b9d78a6ff566d Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 31 Oct 2024 21:31:05 +0000 Subject: [PATCH 050/151] Refactoring a lot CursesDriver. --- .../CursesDriver/CursesDriver.cs | 467 ++++------------- .../CursesDriver/GetTIOCGWINSZ.c | 11 + .../CursesDriver/GetTIOCGWINSZ.sh | 17 + .../CursesDriver/UnixMainLoop.cs | 482 +++++++++++++----- .../ConsoleDrivers/CursesDriver/binding.cs | 13 + .../ConsoleDrivers/CursesDriver/constants.cs | 2 - Terminal.Gui/Terminal.Gui.csproj | 71 +-- .../compiled-binaries/libGetTIOCGWINSZ.dylib | Bin 0 -> 16512 bytes .../compiled-binaries/libGetTIOCGWINSZ.so | Bin 0 -> 15160 bytes 9 files changed, 526 insertions(+), 537 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c create mode 100644 Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh create mode 100644 Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib create mode 100644 Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.so diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 5092d266c..1f4f2bc3f 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -17,7 +17,6 @@ internal class CursesDriver : ConsoleDriver private CursorVisibility? _initialCursorVisibility; private MouseFlags _lastMouseFlags; private UnixMainLoop _mainLoopDriver; - private object _processInputToken; public override int Cols { @@ -42,7 +41,21 @@ internal class CursesDriver : ConsoleDriver public override bool SupportsTrueColor => true; /// - public override bool EnsureCursorVisibility () { return false; } + public override bool EnsureCursorVisibility () + { + if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) + { + GetCursorVisibility (out CursorVisibility cursorVisibility); + _currentCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + return false; + } + + SetCursorVisibility (_currentCursorVisibility ?? CursorVisibility.Default); + + return _currentCursorVisibility == CursorVisibility.Default; + } /// public override bool GetCursorVisibility (out CursorVisibility visibility) @@ -193,6 +206,9 @@ internal class CursesDriver : ConsoleDriver } } + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { @@ -201,33 +217,61 @@ internal class CursesDriver : ConsoleDriver return string.Empty; } - while (Console.KeyAvailable) - { - _mainLoopDriver._forceRead = true; - - _mainLoopDriver._waitForInput.Set (); - _mainLoopDriver._waitForInput.Reset (); - } - - _mainLoopDriver._forceRead = false; - _mainLoopDriver._suspendRead = true; + var response = string.Empty; try { - Console.Out.Write (ansiRequest.Request); - Console.Out.Flush (); // Ensure the request is sent - Task.Delay (100).Wait (); // Allow time for the terminal to respond + lock (ansiRequest._responseLock) + { + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == ansiRequest); - return ReadAnsiResponseDefault (ansiRequest); + ansiRequest.Response = response = e; + + _waitAnsiResponse.Set (); + }; + + _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); + + _mainLoopDriver._forceRead = true; + } + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + _mainLoopDriver._waitForInput.Set (); + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } } - catch (Exception) + catch (OperationCanceledException) { return string.Empty; } finally { - _mainLoopDriver._suspendRead = false; + _mainLoopDriver._forceRead = false; + + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + { + if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.Response)) + { + // Bad request or no response at all + _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + } + } + + _waitAnsiResponse.Reset (); } + + return response; + } + + /// + public override void WriteRaw (string ansi) + { + _mainLoopDriver.WriteRaw (ansi); } public override void Suspend () @@ -254,14 +298,18 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) { - Curses.move (Row, Col); - if (Force16Colors) { + Curses.move (Row, Col); + Curses.raw (); Curses.noecho (); Curses.refresh (); } + else + { + _mainLoopDriver.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + } } } @@ -490,14 +538,13 @@ internal class CursesDriver : ConsoleDriver internal override void End () { + _ansiResponseTokenSource?.Cancel (); + _ansiResponseTokenSource?.Dispose (); + _waitAnsiResponse?.Dispose (); + StopReportingMouseMoves (); SetCursorVisibility (CursorVisibility.Default); - if (_mainLoopDriver is { }) - { - _mainLoopDriver.RemoveWatch (_processInputToken); - } - if (RunningUnitTests) { return; @@ -567,17 +614,6 @@ internal class CursesDriver : ConsoleDriver { Curses.timeout (0); } - - _processInputToken = _mainLoopDriver?.AddWatch ( - 0, - UnixMainLoop.Condition.PollIn, - x => - { - ProcessInput (); - - return true; - } - ); } CurrentAttribute = new Attribute (ColorName16.White, ColorName16.Black); @@ -611,6 +647,7 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests) { Curses.CheckWinChange (); + ClearContents (); if (Force16Colors) { @@ -621,316 +658,48 @@ internal class CursesDriver : ConsoleDriver return new MainLoop (_mainLoopDriver); } - internal void ProcessInput () + internal void ProcessInput (UnixMainLoop.PollData inputEvent) { - int wch; - int code = Curses.get_wch (out wch); - - //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}"); - if (code == Curses.ERR) + switch (inputEvent.EventType) { - return; - } + case UnixMainLoop.EventType.Key: + ConsoleKeyInfo consoleKeyInfo = inputEvent.KeyEvent; - var k = KeyCode.Null; + KeyCode map = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); - if (code == Curses.KEY_CODE_YES) - { - while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) - { - ProcessWinChange (); - code = Curses.get_wch (out wch); - } - - if (wch == 0) - { - return; - } - - if (wch == Curses.KeyMouse) - { - int wch2 = wch; - - while (wch2 == Curses.KeyMouse) + if (map == KeyCode.Null) { - Key kea = null; - - ConsoleKeyInfo [] cki = - { - new ((char)KeyCode.Esc, 0, false, false, false), - new ('[', 0, false, false, false), - new ('<', 0, false, false, false) - }; - code = 0; - HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki); + break; } - return; - } + OnKeyDown (new Key (map)); + OnKeyUp (new Key (map)); - k = MapCursesKey (wch); + break; + case UnixMainLoop.EventType.Mouse: + MouseEventArgs me = new MouseEventArgs { Position = inputEvent.MouseEvent.Position, Flags = inputEvent.MouseEvent.MouseFlags }; + OnMouseEvent (me); - if (wch >= 277 && wch <= 288) - { - // Shift+(F1 - F12) - wch -= 12; - k = KeyCode.ShiftMask | MapCursesKey (wch); - } - else if (wch >= 289 && wch <= 300) - { - // Ctrl+(F1 - F12) - wch -= 24; - k = KeyCode.CtrlMask | MapCursesKey (wch); - } - else if (wch >= 301 && wch <= 312) - { - // Ctrl+Shift+(F1 - F12) - wch -= 36; - k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch); - } - else if (wch >= 313 && wch <= 324) - { - // Alt+(F1 - F12) - wch -= 48; - k = KeyCode.AltMask | MapCursesKey (wch); - } - else if (wch >= 325 && wch <= 327) - { - // Shift+Alt+(F1 - F3) - wch -= 60; - k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch); - } + break; + case UnixMainLoop.EventType.WindowSize: + Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height); + ProcessWinChange (inputEvent.WindowSizeEvent.Size); - OnKeyDown (new Key (k)); - OnKeyUp (new Key (k)); - - return; - } - - // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey - if (wch == 27) - { - Curses.timeout (10); - - code = Curses.get_wch (out int wch2); - - if (code == Curses.KEY_CODE_YES) - { - k = KeyCode.AltMask | MapCursesKey (wch); - } - - Key key = null; - - if (code == 0) - { - // The ESC-number handling, debatable. - // Simulates the AltMask itself by pressing Alt + Space. - // Needed for macOS - if (wch2 == (int)KeyCode.Space) - { - k = KeyCode.AltMask | KeyCode.Space; - } - else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A - && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z) - { - k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space)); - } - else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64) - { - k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64)); - } - else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) - { - k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0)); - } - else - { - ConsoleKeyInfo [] cki = - [ - new ((char)KeyCode.Esc, 0, false, false, false), new ((char)wch2, 0, false, false, false) - ]; - HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); - - return; - } - //else if (wch2 == Curses.KeyCSI) - //{ - // ConsoleKeyInfo [] cki = - // { - // new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false) - // }; - // HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); - - // return; - //} - //else - //{ - // // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. - // if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) - // { - // k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask)); - // } - - // if (wch2 == 0) - // { - // k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space; - // } - // //else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) - // //{ - // // k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space; - // //} - // else if (wch2 < 256) - // { - // k = (KeyCode)wch2; // | KeyCode.AltMask; - // } - // else - // { - // k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2); - // } - //} - - key = new Key (k); - } - else - { - key = Key.Esc; - } - - OnKeyDown (key); - OnKeyUp (key); - } - else if (wch == Curses.KeyTab) - { - k = MapCursesKey (wch); - OnKeyDown (new Key (k)); - OnKeyUp (new Key (k)); - } - else if (wch == 127) - { - // Backspace needed for macOS - k = KeyCode.Backspace; - OnKeyDown (new Key (k)); - OnKeyUp (new Key (k)); - } - else - { - // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. - k = (KeyCode)wch; - - if (wch == 0) - { - k = KeyCode.CtrlMask | KeyCode.Space; - } - else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64) - { - if ((KeyCode)(wch + 64) != KeyCode.J) - { - k = KeyCode.CtrlMask | (KeyCode)(wch + 64); - } - } - else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) - { - k = (KeyCode)wch | KeyCode.ShiftMask; - } - - if (wch == '\n' || wch == '\r') - { - k = KeyCode.Enter; - } - - // Strip the KeyCode.Space flag off if it's set - //if (k != KeyCode.Space && k.HasFlag (KeyCode.Space)) - if (Key.GetIsKeyCodeAtoZ (k) && (k & KeyCode.Space) != 0) - { - k &= ~KeyCode.Space; - } - - OnKeyDown (new Key (k)); - OnKeyUp (new Key (k)); + break; + default: + throw new ArgumentOutOfRangeException (); } } - internal void ProcessWinChange () + private void ProcessWinChange (Size size) { - if (!RunningUnitTests && Curses.CheckWinChange ()) + if (!RunningUnitTests && Curses.ChangeWindowSize (size.Height, size.Width)) { ClearContents (); OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); } } - private void HandleEscSeqResponse ( - ref int code, - ref KeyCode k, - ref int wch2, - ref Key keyEventArgs, - ref ConsoleKeyInfo [] cki - ) - { - ConsoleKey ck = 0; - ConsoleModifiers mod = 0; - - while (code == 0) - { - code = Curses.get_wch (out wch2); - var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false); - - if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) - { - EscSeqUtils.DecodeEscSeq ( - null, - ref consoleKeyInfo, - ref ck, - cki, - ref mod, - out _, - out _, - out _, - out _, - out bool isKeyMouse, - out List mouseFlags, - out Point pos, - out _, - ProcessMouseEvent - ); - - if (isKeyMouse) - { - foreach (MouseFlags mf in mouseFlags) - { - ProcessMouseEvent (mf, pos); - } - - cki = null; - - if (wch2 == 27) - { - cki = EscSeqUtils.ResizeArray ( - new ConsoleKeyInfo ( - (char)KeyCode.Esc, - 0, - false, - false, - false - ), - cki - ); - } - } - else - { - k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); - keyEventArgs = new Key (k); - OnKeyDown (keyEventArgs); - } - } - else - { - cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki); - } - } - } - private static KeyCode MapCursesKey (int cursesKey) { switch (cursesKey) @@ -1008,52 +777,6 @@ internal class CursesDriver : ConsoleDriver } } - private void ProcessMouseEvent (MouseFlags mouseFlag, Point pos) - { - bool WasButtonReleased (MouseFlags flag) - { - return flag.HasFlag (MouseFlags.Button1Released) - || flag.HasFlag (MouseFlags.Button2Released) - || flag.HasFlag (MouseFlags.Button3Released) - || flag.HasFlag (MouseFlags.Button4Released); - } - - bool IsButtonNotPressed (MouseFlags flag) - { - return !flag.HasFlag (MouseFlags.Button1Pressed) - && !flag.HasFlag (MouseFlags.Button2Pressed) - && !flag.HasFlag (MouseFlags.Button3Pressed) - && !flag.HasFlag (MouseFlags.Button4Pressed); - } - - bool IsButtonClickedOrDoubleClicked (MouseFlags flag) - { - return flag.HasFlag (MouseFlags.Button1Clicked) - || flag.HasFlag (MouseFlags.Button2Clicked) - || flag.HasFlag (MouseFlags.Button3Clicked) - || flag.HasFlag (MouseFlags.Button4Clicked) - || flag.HasFlag (MouseFlags.Button1DoubleClicked) - || flag.HasFlag (MouseFlags.Button2DoubleClicked) - || flag.HasFlag (MouseFlags.Button3DoubleClicked) - || flag.HasFlag (MouseFlags.Button4DoubleClicked); - } - - Debug.WriteLine ($"CursesDriver: ({pos.X},{pos.Y}) - {mouseFlag}"); - - - if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) - { - return; - } - - _lastMouseFlags = mouseFlag; - - var me = new MouseEventArgs { Flags = mouseFlag, Position = pos }; - //Debug.WriteLine ($"CursesDriver: ({me.Position}) - {me.Flags}"); - - OnMouseEvent (me); - } - #region Color Handling /// Creates an Attribute from the provided curses-based foreground and background color numbers diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c new file mode 100644 index 000000000..d289b7ef3 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c @@ -0,0 +1,11 @@ +#include +#include + +// This function is used to get the value of the TIOCGWINSZ variable, +// which may have different values ​​on different Unix operating systems. +// In Linux=0x005413, in Darwin and OpenBSD=0x40087468, +// In Solaris=0x005468 +// The best solution is having a function that get the real value of the current OS +int get_tiocgwinsz_value() { + return TIOCGWINSZ; +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh new file mode 100644 index 000000000..e94706eb1 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Create output directory if it doesn't exist +mkdir -p ../../compiled-binaries + +# Determine the output file extension based on the OS +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + OUTPUT_FILE="../../compiled-binaries/libGetTIOCGWINSZ.so" +elif [[ "$OSTYPE" == "darwin"* ]]; then + OUTPUT_FILE="../../compiled-binaries/libGetTIOCGWINSZ.dylib" +else + echo "Unsupported OS: $OSTYPE" + exit 1 +fi + +# Compile the C file +gcc -shared -fPIC -o "$OUTPUT_FILE" GetTIOCGWINSZ.c diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index c7a9a2612..081284358 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -2,6 +2,7 @@ // mainloop.cs: Linux/Curses MainLoop implementation. // +using System.Collections.Concurrent; using System.Runtime.InteropServices; namespace Terminal.Gui; @@ -36,18 +37,13 @@ internal class UnixMainLoop : IMainLoopDriver PollNval = 32 } - public const int KEY_RESIZE = unchecked ((int)0xffffffffffffffff); - private static readonly nint _ignore = Marshal.AllocHGlobal (1); - private readonly CursesDriver _cursesDriver; - private readonly Dictionary _descriptorWatchers = new (); - private readonly int [] _wakeUpPipes = new int [2]; private MainLoop _mainLoop; - private bool _pollDirty = true; private Pollfd [] _pollMap; - private bool _winChanged; + private readonly ConcurrentQueue _pollDataQueue = new (); private readonly ManualResetEventSlim _eventReady = new (false); internal readonly ManualResetEventSlim _waitForInput = new (false); + private readonly ManualResetEventSlim _windowSizeChange = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -57,11 +53,13 @@ internal class UnixMainLoop : IMainLoopDriver _cursesDriver = (CursesDriver)Application.Driver; } + public EscSeqRequests EscSeqRequests { get; } = new (); + void IMainLoopDriver.Wakeup () { if (!ConsoleDriver.RunningUnitTests) { - write (_wakeUpPipes [1], _ignore, 1); + _eventReady.Set (); } } @@ -76,30 +74,99 @@ internal class UnixMainLoop : IMainLoopDriver try { - pipe (_wakeUpPipes); - - AddWatch ( - _wakeUpPipes [0], - Condition.PollIn, - ml => - { - read (_wakeUpPipes [0], _ignore, 1); - - return true; - } - ); + // Setup poll for stdin (fd 0) and pipe (fd 1) + _pollMap = new Pollfd [1]; + _pollMap [0].fd = 0; // stdin (file descriptor 0) + _pollMap [0].events = (short)Condition.PollIn; // Monitor input for reading } catch (DllNotFoundException e) { throw new NotSupportedException ("libc not found", e); } + EscSeqUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed; + Task.Run (CursesInputHandler, _inputHandlerTokenSource.Token); + Task.Run (WindowSizeHandler, _inputHandlerTokenSource.Token); + } + + private static readonly int TIOCGWINSZ = GetTIOCGWINSZValue (); + + private const string PlaceholderLibrary = "compiled-binaries/libGetTIOCGWINSZ"; // Placeholder, won't directly load + + [DllImport (PlaceholderLibrary, EntryPoint = "get_tiocgwinsz_value")] + private static extern int GetTIOCGWINSZValueInternal (); + + public static int GetTIOCGWINSZValue () + { + // Determine the correct library path based on the OS + string libraryPath = Path.Combine ( + AppContext.BaseDirectory, + "compiled-binaries", + RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ? "libGetTIOCGWINSZ.dylib" : "libGetTIOCGWINSZ.so"); + + // Load the native library manually + nint handle = NativeLibrary.Load (libraryPath); + + // Ensure the handle is valid + if (handle == nint.Zero) + { + throw new DllNotFoundException ($"Unable to load library: {libraryPath}"); + } + + return GetTIOCGWINSZValueInternal (); + } + + private void EscSeqUtils_ContinuousButtonPressed (object sender, MouseEventArgs e) + { + _pollDataQueue!.Enqueue (EnqueueMouseEvent (e.Flags, e.Position)); + } + + private void WindowSizeHandler () + { + var ws = new Winsize (); + ioctl (0, TIOCGWINSZ, ref ws); + + // Store initial window size + int rows = ws.ws_row; + int cols = ws.ws_col; + + while (_inputHandlerTokenSource is { IsCancellationRequested: false }) + { + try + { + _windowSizeChange.Wait (_inputHandlerTokenSource.Token); + _windowSizeChange.Reset (); + + while (!_inputHandlerTokenSource.IsCancellationRequested) + { + // Wait for a while then check if screen has changed sizes + Task.Delay (500, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + + ioctl (0, TIOCGWINSZ, ref ws); + + if (rows != ws.ws_row || cols != ws.ws_col) + { + rows = ws.ws_row; + cols = ws.ws_col; + + _pollDataQueue!.Enqueue (EnqueueWindowSizeEvent (rows, cols)); + + break; + } + } + } + catch (OperationCanceledException) + { + return; + } + + _eventReady.Set (); + } } internal bool _forceRead; - internal bool _suspendRead; - private int n; + private int _retries; private void CursesInputHandler () { @@ -107,8 +174,6 @@ internal class UnixMainLoop : IMainLoopDriver { try { - UpdatePollMap (); - if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) { _waitForInput.Wait (_inputHandlerTokenSource.Token); @@ -118,46 +183,184 @@ internal class UnixMainLoop : IMainLoopDriver { return; } - finally + + if (_pollDataQueue?.Count == 0 || _forceRead) { - if (!_inputHandlerTokenSource.IsCancellationRequested) + while (!_inputHandlerTokenSource.IsCancellationRequested) { - _waitForInput.Reset (); - } - } - - while (!_inputHandlerTokenSource.IsCancellationRequested) - { - if (!_suspendRead) - { - n = poll (_pollMap, (uint)_pollMap.Length, 0); - - if (n == KEY_RESIZE) - { - _winChanged = true; - - break; - } + int n = poll (_pollMap, (uint)_pollMap.Length, 0); if (n > 0) { + // Check if stdin has data + if ((_pollMap [0].revents & (int)Condition.PollIn) != 0) + { + // Allocate memory for the buffer + var buf = new byte [2048]; + nint bufPtr = Marshal.AllocHGlobal (buf.Length); + + try + { + // Read from the stdin + int bytesRead = read (_pollMap [0].fd, bufPtr, buf.Length); + + if (bytesRead > 0) + { + // Copy the data from unmanaged memory to a byte array + var buffer = new byte [bytesRead]; + Marshal.Copy (bufPtr, buffer, 0, bytesRead); + + // Convert the byte array to a string (assuming UTF-8 encoding) + string data = Encoding.UTF8.GetString (buffer); + + if (EscSeqUtils.IncompleteCkInfos is { }) + { + data = data.Insert (0, EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos)); + EscSeqUtils.IncompleteCkInfos = null; + } + + // Enqueue the data + ProcessEnqueuePollData (data); + } + } + finally + { + // Free the allocated memory + Marshal.FreeHGlobal (bufPtr); + } + } + + if (_retries > 0) + { + _retries = 0; + } + break; } - } - if (!_forceRead) - { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + { + if (_retries > 1) + { + EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); + + lock (seqReqStatus.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + + try + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } } } + _waitForInput.Reset (); _eventReady.Set (); } } + private void ProcessEnqueuePollData (string pollData) + { + foreach (string split in EscSeqUtils.SplitEscapeRawString (pollData)) + { + EnqueuePollData (split); + } + } + + private void EnqueuePollData (string pollDataPart) + { + ConsoleKeyInfo [] cki = EscSeqUtils.ToConsoleKeyInfoArray (pollDataPart); + + ConsoleKey key = 0; + ConsoleModifiers mod = 0; + ConsoleKeyInfo newConsoleKeyInfo = default; + + EscSeqUtils.DecodeEscSeq ( + EscSeqRequests, + ref newConsoleKeyInfo, + ref key, + cki, + ref mod, + out string c1Control, + out string code, + out string [] values, + out string terminating, + out bool isMouse, + out List mouseFlags, + out Point pos, + out EscSeqReqStatus seqReqStatus, + EscSeqUtils.ProcessMouseEvent + ); + + if (isMouse) + { + foreach (MouseFlags mf in mouseFlags) + { + _pollDataQueue!.Enqueue (EnqueueMouseEvent (mf, pos)); + } + + return; + } + + if (seqReqStatus is { }) + { + var ckiString = EscSeqUtils.ToString (cki); + + lock (seqReqStatus.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = ckiString; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString); + } + + return; + } + + if (newConsoleKeyInfo != default) + { + _pollDataQueue!.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo)); + } + } + + private PollData EnqueueMouseEvent (MouseFlags mouseFlags, Point pos) + { + var mouseEvent = new MouseEvent { Position = pos, MouseFlags = mouseFlags }; + + return new () { EventType = EventType.Mouse, MouseEvent = mouseEvent }; + } + + private PollData EnqueueKeyboardEvent (ConsoleKeyInfo keyInfo) + { + return new () { EventType = EventType.Key, KeyEvent = keyInfo }; + } + + private PollData EnqueueWindowSizeEvent (int rows, int cols) + { + return new () { EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (cols, rows) } }; + } + bool IMainLoopDriver.EventsPending () { _waitForInput.Set (); + _windowSizeChange.Set (); if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) { @@ -182,7 +385,7 @@ internal class UnixMainLoop : IMainLoopDriver if (!_eventReadyTokenSource.IsCancellationRequested) { - return n > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _) || _winChanged; + return _pollDataQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); } return true; @@ -190,52 +393,28 @@ internal class UnixMainLoop : IMainLoopDriver void IMainLoopDriver.Iteration () { - if (_winChanged) + // Dequeue and process the data + while (_pollDataQueue.TryDequeue (out PollData inputRecords)) { - _winChanged = false; - _cursesDriver.ProcessInput (); - - // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426 - _cursesDriver.ProcessWinChange (); - } - - n = 0; - - if (_pollMap is null) - { - return; - } - - foreach (Pollfd p in _pollMap) - { - Watch watch; - - if (p.revents == 0) + if (inputRecords is { }) { - continue; - } - - if (!_descriptorWatchers.TryGetValue (p.fd, out watch)) - { - continue; - } - - if (!watch.Callback (_mainLoop)) - { - _descriptorWatchers.Remove (p.fd); + _cursesDriver.ProcessInput (inputRecords); } } } void IMainLoopDriver.TearDown () { - _descriptorWatchers?.Clear (); + EscSeqUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; _inputHandlerTokenSource?.Cancel (); _inputHandlerTokenSource?.Dispose (); - _waitForInput?.Dispose (); + _windowSizeChange.Dispose(); + + _pollDataQueue?.Clear (); + _eventReadyTokenSource?.Cancel (); _eventReadyTokenSource?.Dispose (); _eventReady?.Dispose (); @@ -243,72 +422,36 @@ internal class UnixMainLoop : IMainLoopDriver _mainLoop = null; } - /// Watches a file descriptor for activity. - /// - /// When the condition is met, the provided callback is invoked. If the callback returns false, the watch is - /// automatically removed. The return value is a token that represents this watch, you can use this token to remove the - /// watch by calling RemoveWatch. - /// - internal object AddWatch (int fileDescriptor, Condition condition, Func callback) + internal void WriteRaw (string ansiRequest) { - if (callback is null) - { - throw new ArgumentNullException (nameof (callback)); - } + // Write to stdout (fd 1) + write (STDOUT_FILENO, ansiRequest, ansiRequest.Length); - var watch = new Watch { Condition = condition, Callback = callback, File = fileDescriptor }; - _descriptorWatchers [fileDescriptor] = watch; - _pollDirty = true; - - return watch; + // Flush the stdout buffer immediately using fsync + fsync (STDOUT_FILENO); } - /// Removes an active watch from the mainloop. - /// The token parameter is the value returned from AddWatch - internal void RemoveWatch (object token) - { - if (!ConsoleDriver.RunningUnitTests) - { - if (token is not Watch watch) - { - return; - } - - _descriptorWatchers.Remove (watch.File); - } - } - - [DllImport ("libc")] - private static extern int pipe ([In] [Out] int [] pipes); - [DllImport ("libc")] private static extern int poll ([In] [Out] Pollfd [] ufds, uint nfds, int timeout); [DllImport ("libc")] private static extern int read (int fd, nint buf, nint n); - private void UpdatePollMap () - { - if (!_pollDirty) - { - return; - } - - _pollDirty = false; - - _pollMap = new Pollfd [_descriptorWatchers.Count]; - var i = 0; - - foreach (int fd in _descriptorWatchers.Keys) - { - _pollMap [i].fd = fd; - _pollMap [i].events = (short)_descriptorWatchers [fd].Condition; - i++; - } - } + // File descriptor for stdout + private const int STDOUT_FILENO = 1; [DllImport ("libc")] - private static extern int write (int fd, nint buf, nint n); + private static extern int write (int fd, string buf, int n); + + [DllImport ("libc", SetLastError = true)] + private static extern int fsync (int fd); + + // Get the stdout pointer for flushing + [DllImport ("libc", SetLastError = true)] + private static extern nint stdout (); + + [DllImport ("libc", SetLastError = true)] + private static extern int ioctl (int fd, int request, ref Winsize ws); [StructLayout (LayoutKind.Sequential)] private struct Pollfd @@ -324,4 +467,75 @@ internal class UnixMainLoop : IMainLoopDriver public Condition Condition; public int File; } + + /// + /// Window or terminal size structure. This information is stored by the kernel in order to provide a consistent + /// interface, but is not used by the kernel. + /// + [StructLayout (LayoutKind.Sequential)] + public struct Winsize + { + public ushort ws_row; // Number of rows + public ushort ws_col; // Number of columns + public ushort ws_xpixel; // Width in pixels (unused) + public ushort ws_ypixel; // Height in pixels (unused) + } + + #region Events + + public enum EventType + { + Key = 1, + Mouse = 2, + WindowSize = 3 + } + + public struct MouseEvent + { + public Point Position; + public MouseFlags MouseFlags; + } + + public struct WindowSizeEvent + { + public Size Size; + } + + public struct PollData + { + public EventType EventType; + public ConsoleKeyInfo KeyEvent; + public MouseEvent MouseEvent; + public WindowSizeEvent WindowSizeEvent; + + public readonly override string ToString () + { + return EventType switch + { + EventType.Key => ToString (KeyEvent), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowSize => WindowSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + }; + } + + /// Prints a ConsoleKeyInfoEx structure + /// + /// + public readonly string ToString (ConsoleKeyInfo cki) + { + var ke = new Key ((KeyCode)cki.KeyChar); + var sb = new StringBuilder (); + sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})"); + sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); + sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) "); + string s = sb.ToString ().TrimEnd (',').TrimEnd (' '); + + return $"[ConsoleKeyInfo({s})]"; + } + } + + #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index 16caaa05c..d79119401 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -143,6 +143,19 @@ public partial class Curses return false; } + public static bool ChangeWindowSize (int l, int c) + { + if (l != lines || c != cols) + { + lines = l; + cols = c; + + return true; + } + + return false; + } + public static int clearok (nint win, bool bf) { return methods.clearok (win, bf); } public static int COLOR_PAIRS () { return methods.COLOR_PAIRS (); } public static int curs_set (int visibility) { return methods.curs_set (visibility); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs index 5700b779f..2984147a2 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs @@ -55,8 +55,6 @@ public partial class Curses public const int COLOR_GRAY = 0x8; public const int KEY_CODE_YES = 0x100; public const int ERR = unchecked ((int)0xffffffff); - public const int TIOCGWINSZ = 0x5413; - public const int TIOCGWINSZ_MAC = 0x40087468; [Flags] public enum Event : long { diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 08813a50f..88d57c26b 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -11,9 +11,9 @@ - - Terminal.Gui - + + Terminal.Gui + @@ -143,35 +143,48 @@ - - - - $(MSBuildThisFileDirectory)..\local_packages\ - - $(MSBuildThisFileDirectory)bin\$(Configuration)\ - + + + + $(MSBuildThisFileDirectory)..\local_packages\ + + $(MSBuildThisFileDirectory)bin\$(Configuration)\ + - - - + + + - - - - + + + + - - + + - - + + - - + + - + + + + + + true + compiled-binaries/ + PreserveNewest + + + + + true + compiled-binaries/ + PreserveNewest + + + \ No newline at end of file diff --git a/Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib b/Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib new file mode 100644 index 0000000000000000000000000000000000000000..06c30292e9f9ec317f59a3dcddc432cba30f553b GIT binary patch literal 16512 zcmeI3ziJdw6voeFH3^ttuu4#5Dn&87i%DS>2Q?&%5(B$p5ag0%C&|EON3yeO(qtP8 z8!PR%*9Wlo0W4Fc6l{C}g~jjOxfh2aV3+E<>^JAnoO@>PZ>BxpfBzayhy)c88Hc8! z;h4xfnSm!V2mOSMX)SNGG*ep9+3`iBRcK|GF`D(R>-*&qHD(S@S`K_e8d62}tS|{0BiL+MY>BE)hjkV^BYWol=kwuY#eFKq5 z9laJ@Tt^%t*0bm{HlqAFd$1ZSzjdg%%Kx!=?|=SP6lmWo>7VPL>PsI!e;m!9oQCOL z+5$U!D8h$O#TIFLPRU>LE0;S4>$371XAnw4iti_dDzS1{pthH0HtyR z(mv~v$RzA77wfsXNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L;5-RD zTYdlI>s~e)z5Hh9tFO6-7p=}y>%2T>#;yG9yT&??J%yb!b~ge)vTfb`62jbZ(o81|* z8ieo|3%DzKSgw#sK*G;vKc&TBYL@?6kw-*@tj1kIz9~Bhn7X%C9+vT@MFQ7EVT?N@ zxI==2+$r&p(IBlqN1o?2C#WMW-&&cr3gc!37nW-T2gze_+x3H&`Y+?{6kM1`=okkQ z<2^_?=H*fwZu5YQ*XTY>37?Rn`h0Q@UNe2$hZNCsoaZu6CcypWqOvOCNDMlDVbtB&*L26movy?7qKo_&by zSE#iM=fr0AYuIdP`tu zuKAkL&Q#fDsr#Gn(ir%)<+?u; z2Y-wvURZGUR;3cp9QT^apCt}{6g}Z5DBu1anSUwY_&tf1cE$0e9rd*y0=(Gy4dY&1 z@CA#z&wDE<)oa|RREx@|TiT~M(Fu_h|E3>*`x`87x|n!=Hu1r^Er}1#&r3v3CO*ID zEvE$ik|0_>J<`%n;{ea&Pj$NFR;BeriG!W1xSM$X2XATOh0ZuBXLgs%GHFkl!k3>XFs z1D6K_k=66=Swok?zqMJj<#LQ<2gxy#7f4Q#PnZ19=$T? ztP~yfsjJ}^xk@DJRQdZQH++};{bD)K`m7Y4`ZegJPfM{WYJ%HDU(CVO_5_0J-hyppwHgT2Y_ zxMf4hS2sMhaiiU_=_WCUGt}x_`#WF#T&}e$#rM03>YZb9zu|8d`*_HHz1Xo{5Pv6i zBJ^JdD;8K#YuxSITNKtq;0^94kBjvO`#wLuSz&#_{wj6q&A-ug9rg1pQ->{Nn3MJL zyyeHYs_^}I&$nM)xh`ax5j*Y^>|ar*Nzp0{@%F7BKx|ux4CFZyO|+`<{(|>6>O`m# zUBzGtu4Q9GpxmCR?yGp^^R?2h^oTnwMqPNCpTYB%QCmFL5#Q^mXvnvUA=>Pu{D3j1IHv5> zWR8*r(km#t;AR{)D%+>P9?y98Xx>w{=T6Wo-xOuLV?zH}iXhMeNN^v5Nc1V6lT9aS z+`PvbX*kDols#N5Dw|e9mTqvJCHkLCyzlU5y=)_YJLhACvSJ^AnU51y;~SmxLA-1y zP*oT8{-C`B<6}RA!N1LWhq40!B!kcSvd4QFM5$G*3Ve7t7?kNp!8`z7b# zMsMqy_9$sQH{jc3qL9-P9iBhN=W{BY6Z>`L^G-hZ@LV8iDm*}cKZLgg2O0YK45wF5 zgzy~#fc!T+FU0Ek#{>X5gNcd^=Q|L>PkXYwpty~ZA zACjXQkLQ3k!6Pn=hdx6cj=}t`Sm3c5;CZa-_9-cBlkkcQ`1KV_&;h<$V(nCi?v#WN ti}M Date: Fri, 1 Nov 2024 14:20:09 +0000 Subject: [PATCH 051/151] Cleanup unused code and comments. --- .../CursesDriver/UnixMainLoop.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 081284358..6bf9f6d8f 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -49,8 +49,7 @@ internal class UnixMainLoop : IMainLoopDriver public UnixMainLoop (ConsoleDriver consoleDriver = null) { - // UnixDriver doesn't use the consoleDriver parameter, but the WindowsDriver does. - _cursesDriver = (CursesDriver)Application.Driver; + _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); } public EscSeqRequests EscSeqRequests { get; } = new (); @@ -74,7 +73,7 @@ internal class UnixMainLoop : IMainLoopDriver try { - // Setup poll for stdin (fd 0) and pipe (fd 1) + // Setup poll for stdin (fd 0) _pollMap = new Pollfd [1]; _pollMap [0].fd = 0; // stdin (file descriptor 0) _pollMap [0].events = (short)Condition.PollIn; // Monitor input for reading @@ -244,7 +243,7 @@ internal class UnixMainLoop : IMainLoopDriver { EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); - lock (seqReqStatus.AnsiRequest._responseLock) + lock (seqReqStatus!.AnsiRequest._responseLock) { seqReqStatus.AnsiRequest.Response = string.Empty; seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); @@ -446,10 +445,6 @@ internal class UnixMainLoop : IMainLoopDriver [DllImport ("libc", SetLastError = true)] private static extern int fsync (int fd); - // Get the stdout pointer for flushing - [DllImport ("libc", SetLastError = true)] - private static extern nint stdout (); - [DllImport ("libc", SetLastError = true)] private static extern int ioctl (int fd, int request, ref Winsize ws); @@ -461,13 +456,6 @@ internal class UnixMainLoop : IMainLoopDriver public readonly short revents; } - private class Watch - { - public Func Callback; - public Condition Condition; - public int File; - } - /// /// Window or terminal size structure. This information is stored by the kernel in order to provide a consistent /// interface, but is not used by the kernel. From 126bcef1119e950f1cf96f3e60db1e53d63b0620 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 1 Nov 2024 20:08:49 +0000 Subject: [PATCH 052/151] Add EscSeqRequests support to WindowsDriver. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 31 --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 221 ++++++++++++++----- 2 files changed, 168 insertions(+), 84 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e3cdb7252..b37c50c53 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -621,37 +621,6 @@ public abstract class ConsoleDriver /// public abstract void WriteRaw (string ansi); - internal string ReadAnsiResponseDefault (AnsiEscapeSequenceRequest ansiRequest) - { - var response = new StringBuilder (); - var index = 0; - - while (Console.KeyAvailable) - { - // Peek the next key - ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console - - if (index == 0 && keyInfo.KeyChar != EscSeqUtils.KeyEsc) - { - continue; - } - - response.Append (keyInfo.KeyChar); - - // Read until no key is available if no terminator was specified or - // check if the key is terminator (ANSI escape sequence ends) - if (!string.IsNullOrEmpty (ansiRequest.Terminator) && keyInfo.KeyChar == ansiRequest.Terminator [^1]) - { - // Break out of the loop when terminator is found - break; - } - - index++; - } - - return response.ToString (); - } - #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 57848301a..c19d6376d 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -24,6 +24,8 @@ namespace Terminal.Gui; internal class WindowsConsole { + internal WindowsMainLoop _mainLoop; + public const int STD_OUTPUT_HANDLE = -11; public const int STD_INPUT_HANDLE = -10; @@ -149,7 +151,13 @@ internal class WindowsConsole internal bool WriteANSI (string ansi) { - return WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, nint.Zero); + if (WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, nint.Zero)) + { + // Flush the output to make sure it's sent immediately + return FlushFileBuffers (_screenBuffer); + } + + return false; } public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) @@ -800,7 +808,7 @@ internal class WindowsConsole [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] public static extern bool ReadConsoleInput ( nint hConsoleInput, - nint lpBuffer, + out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead ); @@ -833,6 +841,9 @@ internal class WindowsConsole nint lpReserved ); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool FlushFileBuffers (nint hFile); + [DllImport ("kernel32.dll")] private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition); @@ -900,35 +911,110 @@ internal class WindowsConsole } } + private int _retries; + public InputRecord [] ReadConsoleInput () { const int bufferSize = 1; - nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize); + InputRecord inputRecord = default; uint numberEventsRead = 0; + StringBuilder ansiSequence = new StringBuilder (); + bool readingSequence = false; - try + while (true) { - - if (PeekConsoleInput (_inputHandle, out InputRecord inputRecord, 1, out uint eventsRead) && eventsRead > 0) + try { - ReadConsoleInput ( - _inputHandle, - pRecord, - bufferSize, - out numberEventsRead); - } + // Peek to check if there is any input available + if (PeekConsoleInput (_inputHandle, out _, bufferSize, out uint eventsRead) && eventsRead > 0) + { + // Read the input since it is available + ReadConsoleInput ( + _inputHandle, + out inputRecord, + bufferSize, + out numberEventsRead); - return numberEventsRead == 0 - ? null - : new [] { Marshal.PtrToStructure (pRecord) }; - } - catch (Exception) - { - return null; - } - finally - { - Marshal.FreeHGlobal (pRecord); + if (inputRecord.EventType == EventType.Key) + { + KeyEventRecord keyEvent = inputRecord.KeyEvent; + + if (keyEvent.bKeyDown) + { + char inputChar = keyEvent.UnicodeChar; + + // Check if input is part of an ANSI escape sequence + if (inputChar == '\u001B') // Escape character + { + // Peek to check if there is any input available with key event and bKeyDown + if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, bufferSize, out eventsRead) && eventsRead > 0) + { + if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) + { + // It's really an ANSI request response + readingSequence = true; + ansiSequence.Clear (); // Start a new sequence + ansiSequence.Append (inputChar); + + continue; + } + } + } + else if (readingSequence) + { + ansiSequence.Append (inputChar); + + // Check if the sequence has ended with an expected command terminator + if (_mainLoop.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out EscSeqReqStatus seqReqStatus)) + { + // Finished reading the sequence and remove the enqueued request + _mainLoop.EscSeqRequests.Remove (seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); + } + } + + continue; + } + } + } + } + + if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + { + if (_retries > 1) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + + return numberEventsRead == 0 + ? null + : [inputRecord]; + } + catch (Exception) + { + return null; + } } } @@ -1191,6 +1277,9 @@ internal class WindowsDriver : ConsoleDriver } } + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { @@ -1199,44 +1288,66 @@ internal class WindowsDriver : ConsoleDriver return string.Empty; } - while (Console.KeyAvailable) - { - _mainLoopDriver._forceRead = true; - - _mainLoopDriver._waitForProbe.Set (); - _mainLoopDriver._waitForProbe.Reset (); - } - - _mainLoopDriver._forceRead = false; - _mainLoopDriver._suspendRead = true; + var response = string.Empty; try { - WriteRaw (ansiRequest.Request); + lock (ansiRequest._responseLock) + { + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == ansiRequest); - Thread.Sleep (100); // Allow time for the terminal to respond + ansiRequest.Response = response = e; - return ReadAnsiResponseDefault (ansiRequest); + _waitAnsiResponse.Set (); + }; + + _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); + + _mainLoopDriver._forceRead = true; + } + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + _mainLoopDriver._waitForProbe.Set (); + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } } - catch (Exception) + catch (OperationCanceledException) { return string.Empty; } finally { - _mainLoopDriver._suspendRead = false; + _mainLoopDriver._forceRead = false; + + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + { + if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.Response)) + { + // Bad request or no response at all + _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + } + } + + _waitAnsiResponse.Reset (); } + + return response; } - #region Not Implemented - - public override void Suspend () { throw new NotImplementedException (); } - public override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } + #region Not Implemented + + public override void Suspend () { throw new NotImplementedException (); } + #endregion public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) @@ -2234,8 +2345,11 @@ internal class WindowsMainLoop : IMainLoopDriver { _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); _winConsole = ((WindowsDriver)consoleDriver).WinConsole; + _winConsole._mainLoop = this; } + public EscSeqRequests EscSeqRequests { get; } = new (); + void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; @@ -2343,7 +2457,6 @@ internal class WindowsMainLoop : IMainLoopDriver } internal bool _forceRead; - internal bool _suspendRead; private void WindowsInputHandler () { @@ -2351,7 +2464,7 @@ internal class WindowsMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested) + if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) { _waitForProbe.Wait (_inputHandlerTokenSource.Token); } @@ -2377,21 +2490,23 @@ internal class WindowsMainLoop : IMainLoopDriver { while (!_inputHandlerTokenSource.IsCancellationRequested) { - if (!_suspendRead) + WindowsConsole.InputRecord [] inpRec = _winConsole.ReadConsoleInput (); + + if (inpRec is { }) { - WindowsConsole.InputRecord[] inpRec = _winConsole.ReadConsoleInput (); + _resultQueue!.Enqueue (inpRec); - if (inpRec is { }) - { - _resultQueue!.Enqueue (inpRec); - - break; - } + break; } if (!_forceRead) { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + try + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + catch (OperationCanceledException) + { } } } } From 5b39c3d5a6e7db76fafd07add5f6ca35625948de Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 1 Nov 2024 20:38:22 +0000 Subject: [PATCH 053/151] Fix unit test. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index c19d6376d..6e9c7e096 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -2345,7 +2345,11 @@ internal class WindowsMainLoop : IMainLoopDriver { _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); _winConsole = ((WindowsDriver)consoleDriver).WinConsole; - _winConsole._mainLoop = this; + + if (!ConsoleDriver.RunningUnitTests) + { + _winConsole._mainLoop = this; + } } public EscSeqRequests EscSeqRequests { get; } = new (); From c89efe554358d4b2cf2e0582d5bfe6845bb3f20b Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Nov 2024 07:51:25 +0000 Subject: [PATCH 054/151] Add tab view for single/multi request sends --- .../Scenarios/AnsiEscapeSequenceRequests.cs | 77 +++++++++++++++---- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 4d3f4dd0b..a56cd0c27 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -12,12 +12,49 @@ public sealed class AnsiEscapeSequenceRequests : Scenario // Init Application.Init (); + TabView tv = new TabView + { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + Tab single = new Tab (); + single.DisplayText = "Single"; + single.View = BuildSingleTab (); + + Tab bulk = new (); + bulk.DisplayText = "Multi"; + bulk.View = BuildBulkTab (); + + tv.AddTab (single, true); + tv.AddTab (bulk, false); + // Setup - Create a top-level application window and configure it. Window appWindow = new () { Title = GetQuitKeyAndName (), }; - appWindow.Padding.Thickness = new (1); + + appWindow.Add (tv); + + // Run - Start the application. + Application.Run (appWindow); + bulk.View.Dispose (); + single.View.Dispose (); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); + } + private View BuildSingleTab () + { + View w = new View () + { + Width = Dim.Fill(), + Height = Dim.Fill () + }; + + w.Padding.Thickness = new (1); var scrRequests = new List { @@ -28,19 +65,19 @@ public sealed class AnsiEscapeSequenceRequests : Scenario }; var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) }; - appWindow.Add (cbRequests); + w.Add (cbRequests); var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:" }; var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 }; - appWindow.Add (label, tfRequest); + w.Add (label, tfRequest); label = new Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "Value:" }; var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 }; - appWindow.Add (label, tfValue); + w.Add (label, tfValue); label = new Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "Terminator:" }; var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 }; - appWindow.Add (label, tfTerminator); + w.Add (label, tfTerminator); cbRequests.SelectedItemChanged += (s, e) => { @@ -76,24 +113,24 @@ public sealed class AnsiEscapeSequenceRequests : Scenario label = new Label { Y = Pos.Bottom (tfRequest) + 2, Text = "Response:" }; var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; - appWindow.Add (label, tvResponse); + w.Add (label, tvResponse); label = new Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "Error:" }; var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; - appWindow.Add (label, tvError); + w.Add (label, tvError); label = new Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "Value:" }; var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true }; - appWindow.Add (label, tvValue); + w.Add (label, tvValue); label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "Terminator:" }; var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true }; - appWindow.Add (label, tvTerminator); + w.Add (label, tvTerminator); var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request", IsDefault = true }; var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 }; - appWindow.Add (lblSuccess); + w.Add (lblSuccess); btnResponse.Accepting += (s, e) => { @@ -125,15 +162,21 @@ public sealed class AnsiEscapeSequenceRequests : Scenario lblSuccess.Text = "Error"; } }; - appWindow.Add (btnResponse); + w.Add (btnResponse); - appWindow.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "You can send other requests by editing the TextFields." }); + w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "You can send other requests by editing the TextFields." }); - // Run - Start the application. - Application.Run (appWindow); - appWindow.Dispose (); + return w; + } - // Shutdown - Calling Application.Shutdown is required. - Application.Shutdown (); + private View BuildBulkTab () + { + View w = new View () + { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + return w; } } From 0ddc11c41b19dd2207b9d973b266f744412a03ab Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Nov 2024 07:55:22 +0000 Subject: [PATCH 055/151] Add bulk send to scenario --- .../Scenarios/AnsiEscapeSequenceRequests.cs | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index a56cd0c27..c1f6f8930 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Generic; +using System.Linq; +using System.Text; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -7,6 +10,18 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Ansi Escape Sequence")] public sealed class AnsiEscapeSequenceRequests : Scenario { + private GraphView _graphView; + + private DateTime start = DateTime.Now; + private ScatterSeries _sentSeries; + private ScatterSeries _answeredSeries; + + private List sends = new (); + + private object lockAnswers = new object (); + private Dictionary answers = new (); + private Label _lblSummary; + public override void Main () { // Init @@ -177,6 +192,184 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Height = Dim.Fill () }; + var lbl = new Label () + { + Text = "This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends", + Height = 2, + Width = Dim.Fill () + }; + + Application.AddTimeout ( + TimeSpan.FromMilliseconds (1000), + () => + { + lock (lockAnswers) + { + UpdateGraph (); + + UpdateResponses (); + } + + + + return true; + }); + + var tv = new TextView () + { + Y = Pos.Bottom (lbl), + Width = Dim.Percent (50), + Height = Dim.Fill () + }; + + + var lblDar = new Label () + { + Y = Pos.Bottom (lbl), + X = Pos.Right (tv) + 1, + Text = "DAR per second", + }; + var cbDar = new NumericUpDown () + { + X = Pos.Right (lblDar), + Y = Pos.Bottom (lbl), + Value = 0, + }; + + cbDar.ValueChanging += (s, e) => + { + if (e.NewValue < 0 || e.NewValue > 20) + { + e.Cancel = true; + } + }; + w.Add (cbDar); + + int lastSendTime = Environment.TickCount; + object lockObj = new object (); + Application.AddTimeout ( + TimeSpan.FromMilliseconds (50), + () => + { + lock (lockObj) + { + if (cbDar.Value > 0) + { + int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds + int currentTime = Environment.TickCount; // Current system time in milliseconds + + // Check if the time elapsed since the last send is greater than the interval + if (currentTime - lastSendTime >= interval) + { + SendDar (); // Send the request + lastSendTime = currentTime; // Update the last send time + } + } + } + + return true; + }); + + + _graphView = new GraphView () + { + Y = Pos.Bottom (cbDar), + X = Pos.Right (tv), + Width = Dim.Fill (), + Height = Dim.Fill (1) + }; + + _lblSummary = new Label () + { + Y = Pos.Bottom (_graphView), + X = Pos.Right (tv), + Width = Dim.Fill () + }; + + SetupGraph (); + + w.Add (lbl); + w.Add (lblDar); + w.Add (cbDar); + w.Add (tv); + w.Add (_graphView); + w.Add (_lblSummary); + return w; } + private void UpdateResponses () + { + _lblSummary.Text = GetSummary (); + _lblSummary.SetNeedsDisplay (); + } + + private string GetSummary () + { + if (answers.Count == 0) + { + return "No requests sent yet"; + } + + var last = answers.Last ().Value; + + var unique = answers.Values.Distinct ().Count (); + var total = answers.Count; + + return $"Last:{last} U:{unique} T:{total}"; + } + + private void SetupGraph () + { + + _graphView.Series.Add (_sentSeries = new ScatterSeries ()); + _graphView.Series.Add (_answeredSeries = new ScatterSeries ()); + + _sentSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightGreen, ColorName16.Black)); + _answeredSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightRed, ColorName16.Black)); + + // Todo: + // _graphView.Annotations.Add (_sentSeries new PathAnnotation {}); + + _graphView.CellSize = new PointF (1, 1); + _graphView.MarginBottom = 2; + _graphView.AxisX.Increment = 1; + _graphView.AxisX.Text = "Seconds"; + _graphView.GraphColor = new Attribute (Color.Green, Color.Black); + } + + private void UpdateGraph () + { + _sentSeries.Points = sends + .GroupBy (ToSeconds) + .Select (g => new PointF (g.Key, g.Count ())) + .ToList (); + + _answeredSeries.Points = answers.Keys + .GroupBy (ToSeconds) + .Select (g => new PointF (g.Key, g.Count ())) + .ToList (); + // _graphView.ScrollOffset = new PointF(,0); + _graphView.SetNeedsDisplay (); + + } + + private int ToSeconds (DateTime t) + { + return (int)(DateTime.Now - t).TotalSeconds; + } + + private void SendDar () + { + sends.Add (DateTime.Now); + var result = Application.Driver.WriteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes); + HandleResponse (result); + } + + private void HandleResponse (string response) + { + lock (lockAnswers) + { + answers.Add (DateTime.Now, response); + } + } } From 64ad1a9901e2dc58041a8e96a114500dc53191ef Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Nov 2024 17:16:07 +0000 Subject: [PATCH 056/151] Fix a bug which was sending the terminator to the mainloop. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 6e9c7e096..f6c0c53ed 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -974,6 +974,8 @@ internal class WindowsConsole { seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); } } From a2872cdb9adda020111bb06327aad4d8b724c8ae Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Nov 2024 17:17:36 +0000 Subject: [PATCH 057/151] Ensures a new iteration when _eventReady is already set. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index f6c0c53ed..d3bad7059 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -2517,7 +2517,15 @@ internal class WindowsMainLoop : IMainLoopDriver } } - _eventReady.Set (); + if (_eventReady.IsSet) + { + // it's already in an iteration and ensures set to iterate again + Application.Invoke (() => _eventReady.Set ()); + } + else + { + _eventReady.Set (); + } } } From 68b41a3962d87221a81176bf0288f5fb2a8db0fa Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Nov 2024 18:52:23 +0000 Subject: [PATCH 058/151] Set CanFocus true and only SetNeedsDisplay if were sent or answered. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index c1f6f8930..e23203a74 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -66,7 +66,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario View w = new View () { Width = Dim.Fill(), - Height = Dim.Fill () + Height = Dim.Fill (), + CanFocus = true }; w.Padding.Thickness = new (1); @@ -189,7 +190,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario View w = new View () { Width = Dim.Fill (), - Height = Dim.Fill () + Height = Dim.Fill (), + CanFocus = true }; var lbl = new Label () @@ -349,7 +351,10 @@ public sealed class AnsiEscapeSequenceRequests : Scenario .Select (g => new PointF (g.Key, g.Count ())) .ToList (); // _graphView.ScrollOffset = new PointF(,0); - _graphView.SetNeedsDisplay (); + if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) + { + _graphView.SetNeedsDisplay (); + } } From c999fc0028a4f642d45ba748525c22627ba640ee Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Nov 2024 21:06:43 +0000 Subject: [PATCH 059/151] Trying fix unit tests. --- Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 8 +------- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 6 ++++++ Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 8 +++++++- UnitTests/Application/SynchronizatonContextTests.cs | 7 ++++--- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 6bf9f6d8f..52364f5db 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -54,13 +54,7 @@ internal class UnixMainLoop : IMainLoopDriver public EscSeqRequests EscSeqRequests { get; } = new (); - void IMainLoopDriver.Wakeup () - { - if (!ConsoleDriver.RunningUnitTests) - { - _eventReady.Set (); - } - } + void IMainLoopDriver.Wakeup () { _eventReady.Set (); } void IMainLoopDriver.Setup (MainLoop mainLoop) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index df57c6b2f..10c41d127 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1844,6 +1844,12 @@ internal class NetMainLoop : IMainLoopDriver void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; + + if (ConsoleDriver.RunningUnitTests) + { + return; + } + Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index d3bad7059..271509960 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -2346,10 +2346,10 @@ internal class WindowsMainLoop : IMainLoopDriver public WindowsMainLoop (ConsoleDriver consoleDriver = null) { _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); - _winConsole = ((WindowsDriver)consoleDriver).WinConsole; if (!ConsoleDriver.RunningUnitTests) { + _winConsole = ((WindowsDriver)consoleDriver).WinConsole; _winConsole._mainLoop = this; } } @@ -2359,6 +2359,12 @@ internal class WindowsMainLoop : IMainLoopDriver void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; + + if (ConsoleDriver.RunningUnitTests) + { + return; + } + Task.Run (WindowsInputHandler, _inputHandlerTokenSource.Token); #if HACK_CHECK_WINCHANGED Task.Run (CheckWinChange); diff --git a/UnitTests/Application/SynchronizatonContextTests.cs b/UnitTests/Application/SynchronizatonContextTests.cs index fce4a3250..9f9804649 100644 --- a/UnitTests/Application/SynchronizatonContextTests.cs +++ b/UnitTests/Application/SynchronizatonContextTests.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui.ApplicationTests; public class SyncrhonizationContextTests { - [Fact(Skip = "Causes ubuntu to crash in github action.")] + [Fact] public void SynchronizationContext_CreateCopy () { Application.Init (); @@ -20,11 +20,12 @@ public class SyncrhonizationContextTests [Theory] [InlineData (typeof (FakeDriver))] - //[InlineData (typeof (NetDriver))] + [InlineData (typeof (NetDriver))] [InlineData (typeof (WindowsDriver))] - //[InlineData (typeof (CursesDriver))] + [InlineData (typeof (CursesDriver))] public void SynchronizationContext_Post (Type driverType) { + ConsoleDriver.RunningUnitTests = true; Application.Init (driverName: driverType.Name); SynchronizationContext context = SynchronizationContext.Current; From 3c4564c9079b31139a77f08359310aa980836f7c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Nov 2024 21:12:08 +0000 Subject: [PATCH 060/151] Explain what the colors represent. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index e23203a74..321af7c2e 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -196,7 +196,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario var lbl = new Label () { - Text = "This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends", + Text = "This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.", Height = 2, Width = Dim.Fill () }; From 2e0bc0162d12172b7a0af65bc0fd77bb5a98811b Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 00:29:34 +0000 Subject: [PATCH 061/151] Fixes #3807. WindowsDriver doesn't process characters with accents. --- Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs | 5 +++-- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 7 ++++--- Terminal.Gui/Input/Key.cs | 15 +++++++++++---- UnitTests/Input/KeyTests.cs | 12 +++++------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index 25f87bc56..503e7cd57 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -249,7 +249,7 @@ public static class ConsoleKeyMapping { var modifiers = new ConsoleModifiers (); - if (key.HasFlag (KeyCode.ShiftMask)) + if (key.HasFlag (KeyCode.ShiftMask) || char.IsUpper ((char)key)) { modifiers |= ConsoleModifiers.Shift; } @@ -590,7 +590,8 @@ public static class ConsoleKeyMapping if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) { - consoleKey = char.ToUpper (stFormD [i]); + char ck = char.ToUpper (stFormD [i]); + consoleKey = (uint)(ck > 0 && ck <= 255 ? char.ToUpper (stFormD [i]) : 0); scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0); if (scode is { }) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 271509960..1b9085e23 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1891,12 +1891,13 @@ internal class WindowsDriver : ConsoleDriver // If (ShiftMask is on and CapsLock is off) or (ShiftMask is off and CapsLock is on) add the ShiftMask if (char.IsUpper (keyInfo.KeyChar)) { - return (KeyCode)(uint)keyInfo.Key | KeyCode.ShiftMask; + // Always return the KeyChar because it may be an À, À with Oem1, etc + return (KeyCode)keyInfo.KeyChar | KeyCode.ShiftMask; } } - // Return the Key (not KeyChar!) - return (KeyCode)keyInfo.Key; + // Always return the KeyChar because it may be an á, à with Oem1, etc + return (KeyCode)keyInfo.KeyChar; } // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index d69552aa6..3c8b75d94 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using Terminal.Gui.ConsoleDrivers; namespace Terminal.Gui; @@ -288,7 +289,10 @@ public class Key : EventArgs, IEquatable return false; } - if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z) + // A to Z may have , , , , with Oem1, etc + ConsoleKeyInfo cki = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (keyCode); + + if (cki.Key is >= ConsoleKey.A and <= ConsoleKey.Z) { return true; } @@ -521,9 +525,12 @@ public class Key : EventArgs, IEquatable // Handle special cases and modifiers on their own if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers)) { - if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) + // A to Z may have , , , , with Oem1, etc + ConsoleKeyInfo cki = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (baseKey); + + if ((key & KeyCode.SpecialMask) != 0 && cki.Key is >= ConsoleKey.A and <= ConsoleKey.Z) { - sb.Append (baseKey & ~KeyCode.Space); + sb.Append (((char)(baseKey & ~KeyCode.Space)).ToString ()); } else { @@ -706,7 +713,7 @@ public class Key : EventArgs, IEquatable if (GetIsKeyCodeAtoZ (keyCode) && (keyCode & KeyCode.Space) != 0) { - keyCode = keyCode & ~KeyCode.Space; + keyCode &= ~KeyCode.Space; } key = new (keyCode | modifiers); diff --git a/UnitTests/Input/KeyTests.cs b/UnitTests/Input/KeyTests.cs index fa2695e5f..1916c1d9b 100644 --- a/UnitTests/Input/KeyTests.cs +++ b/UnitTests/Input/KeyTests.cs @@ -17,7 +17,7 @@ public class KeyTests { "Alt+A", Key.A.WithAlt }, { "Shift+A", Key.A.WithShift }, { "A", Key.A.WithShift }, - { "â", new ((KeyCode)'â') }, + { "â", new ((KeyCode)'Â') }, { "Shift+â", new ((KeyCode)'â' | KeyCode.ShiftMask) }, { "Shift+Â", new ((KeyCode)'Â' | KeyCode.ShiftMask) }, { "Ctrl+Shift+CursorUp", Key.CursorUp.WithShift.WithCtrl }, @@ -342,12 +342,10 @@ public class KeyTests [InlineData ((KeyCode)'{', "{")] [InlineData ((KeyCode)'\'', "\'")] [InlineData ((KeyCode)'ó', "ó")] - [InlineData ( - (KeyCode)'Ó' | KeyCode.ShiftMask, - "Shift+Ó" - )] // TODO: This is not correct, it should be Shift+ó or just Ó + [InlineData ((KeyCode)'Ó' | KeyCode.ShiftMask, "Ó")] + [InlineData ((KeyCode)'ó' | KeyCode.ShiftMask, "Ó")] [InlineData ((KeyCode)'Ó', "Ó")] - [InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+ç")] + [InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+Ç")] [InlineData ((KeyCode)'a', "a")] // 97 or Key.Space | Key.A [InlineData ((KeyCode)'A', "a")] // 65 or equivalent to Key.A, but A-Z are mapped to lower case by drivers [InlineData (KeyCode.ShiftMask | KeyCode.A, "A")] @@ -470,7 +468,7 @@ public class KeyTests [InlineData ("Alt+A", KeyCode.A | KeyCode.AltMask)] [InlineData ("Shift+A", KeyCode.A | KeyCode.ShiftMask)] [InlineData ("A", KeyCode.A | KeyCode.ShiftMask)] - [InlineData ("â", (KeyCode)'â')] + [InlineData ("â", (KeyCode)'Â')] [InlineData ("Shift+â", (KeyCode)'â' | KeyCode.ShiftMask)] [InlineData ("Shift+Â", (KeyCode)'Â' | KeyCode.ShiftMask)] [InlineData ("Ctrl+Shift+CursorUp", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.CursorUp)] From 2d8bfd51a17bb1d4c62a6a85468d833d27033d12 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 00:34:51 +0000 Subject: [PATCH 062/151] Fix comment. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 1b9085e23..2c62de466 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1891,7 +1891,7 @@ internal class WindowsDriver : ConsoleDriver // If (ShiftMask is on and CapsLock is off) or (ShiftMask is off and CapsLock is on) add the ShiftMask if (char.IsUpper (keyInfo.KeyChar)) { - // Always return the KeyChar because it may be an À, À with Oem1, etc + // Always return the KeyChar because it may be an Á, À with Oem1, etc return (KeyCode)keyInfo.KeyChar | KeyCode.ShiftMask; } } From 808896d8002f44ded06982af5e73c7ed6a4f7031 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 18:34:38 +0000 Subject: [PATCH 063/151] Fix bug where some key were not been split. --- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 6 +++++- UnitTests/Input/EscSeqUtilsTests.cs | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index cd7a99139..ab7f4bb04 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -338,6 +338,8 @@ public static class EscSeqUtils if (!string.IsNullOrEmpty (terminator)) { + System.Diagnostics.Debug.Assert (terminator.Length == 1); + key = GetConsoleKey (terminator [0], values [0], ref mod, ref keyChar); if (key != 0 && values.Length > 1) @@ -1417,7 +1419,9 @@ public static class EscSeqUtils split = AddAndClearSplit (); splitList.Add (c.ToString ()); } - else if (previousChar != '\u001B' && c < Key.Space)// uint n when n is > 0 and <= KeyEsc + else if ((previousChar != '\u001B' && c <= Key.Space) || (previousChar != '\u001B' && c == 127) + || (char.IsLetter (previousChar) && char.IsLower (c) && char.IsLetter (c)) + || (!string.IsNullOrEmpty (split) && split.Length > 2 && char.IsLetter (previousChar) && char.IsLetter (c))) { isEscSeq = false; split = AddAndClearSplit (); diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 5991cd26c..3b028b60e 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -1440,12 +1440,14 @@ public class EscSeqUtilsTests Assert.Equal (cki, expectedCkInfos [0]); } - [Fact] - public void SplitEscapeRawString_Multiple_Tests () + [Theory] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\r", "\r")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCe", "e")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCV", "V")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\u007f", "\u007f")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC ", " ")] + public void SplitEscapeRawString_Multiple_Tests (string rawData, string expectedLast) { - string rawData = - "\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\r"; - List splitList = EscSeqUtils.SplitEscapeRawString (rawData); Assert.Equal (18, splitList.Count); Assert.Equal ("\r", splitList [0]); @@ -1465,7 +1467,7 @@ public class EscSeqUtilsTests Assert.Equal ("\u001b[<0;33;6M", splitList [14]); Assert.Equal ("\u001b[<0;33;6m", splitList [15]); Assert.Equal ("\u001bOC", splitList [16]); - Assert.Equal ("\r", splitList [^1]); + Assert.Equal (expectedLast, splitList [^1]); } [Theory] From 782325f6b73d519400bc9f2ac8fe6facef80941f Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 18:36:16 +0000 Subject: [PATCH 064/151] Change to ConcurrentQueue. --- Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index 4dc98937b..c96c8f08b 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Collections.Concurrent; + namespace Terminal.Gui; /// @@ -95,5 +97,5 @@ public class EscSeqRequests } /// Gets the list. - public Queue Statuses { get; } = new (); + public ConcurrentQueue Statuses { get; } = new (); } From e2f3b7b5e13a1186d566930cc4eae74a1c98f23e Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 18:37:27 +0000 Subject: [PATCH 065/151] Using empty string instead of space. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 76bd24101..662736e93 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -82,7 +82,7 @@ public class AnsiEscapeSequenceRequest if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) { - char resp = string.IsNullOrEmpty (ansiRequest.Response) ? ' ' : ansiRequest.Response.Last (); + string resp = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response.Last ().ToString (); throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{ansiRequest.Terminator [^1]}'"); } From e35b37fb4d65187044ce032125772b5742ff0297 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 20:01:49 +0000 Subject: [PATCH 066/151] Add InvalidRequestTerminator property. --- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index ab7f4bb04..59c10b548 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -163,6 +163,11 @@ public static class EscSeqUtils /// public static ConsoleKeyInfo []? IncompleteCkInfos { get; set; } + /// + /// Represent a response that was requested by an invalid terminator. + /// + public static string? InvalidRequestTerminator { get; set; } + /// /// Decodes an ANSI escape sequence. /// @@ -347,12 +352,22 @@ public static class EscSeqUtils mod |= GetConsoleModifiers (values [1]); } - newConsoleKeyInfo = new ( - keyChar, - key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); + if (keyChar != 0 || key != 0 || mod != 0) + { + newConsoleKeyInfo = new ( + keyChar, + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + } + else + { + // It's request response that wasn't handled by a valid request terminator + System.Diagnostics.Debug.Assert (escSeqRequests is { Statuses.Count: > 0 }); + + InvalidRequestTerminator = ToString (cki); + } } else { From da21ef1320848fe2ab34afe3e9596f32b87b6b7a Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 20:04:50 +0000 Subject: [PATCH 067/151] Improves drivers responses to being more reliable. --- .../CursesDriver/UnixMainLoop.cs | 48 +++++++++--- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 78 +++++++++++++------ Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 53 ++++++++----- 3 files changed, 121 insertions(+), 58 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 52364f5db..52014cb55 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -176,6 +176,13 @@ internal class UnixMainLoop : IMainLoopDriver { return; } + finally + { + if (!_inputHandlerTokenSource.IsCancellationRequested) + { + _waitForInput.Reset (); + } + } if (_pollDataQueue?.Count == 0 || _forceRead) { @@ -235,12 +242,15 @@ internal class UnixMainLoop : IMainLoopDriver { if (_retries > 1) { - EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) + if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { - seqReqStatus.AnsiRequest.Response = string.Empty; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + lock (seqReqStatus!.AnsiRequest._responseLock) + { + EscSeqRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } } _retries = 0; @@ -257,7 +267,10 @@ internal class UnixMainLoop : IMainLoopDriver try { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + if (!_forceRead) + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } } catch (OperationCanceledException) { @@ -266,7 +279,6 @@ internal class UnixMainLoop : IMainLoopDriver } } - _waitForInput.Reset (); _eventReady.Set (); } } @@ -327,6 +339,22 @@ internal class UnixMainLoop : IMainLoopDriver return; } + if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator)) + { + if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result)) + { + lock (result.AnsiRequest._responseLock) + { + result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator; + result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator); + + EscSeqUtils.InvalidRequestTerminator = null; + } + } + + return; + } + if (newConsoleKeyInfo != default) { _pollDataQueue!.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo)); @@ -420,8 +448,7 @@ internal class UnixMainLoop : IMainLoopDriver // Write to stdout (fd 1) write (STDOUT_FILENO, ansiRequest, ansiRequest.Length); - // Flush the stdout buffer immediately using fsync - fsync (STDOUT_FILENO); + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); } [DllImport ("libc")] @@ -436,9 +463,6 @@ internal class UnixMainLoop : IMainLoopDriver [DllImport ("libc")] private static extern int write (int fd, string buf, int n); - [DllImport ("libc", SetLastError = true)] - private static extern int fsync (int fd); - [DllImport ("libc", SetLastError = true)] private static extern int ioctl (int fd, int request, ref Winsize ws); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 10c41d127..3afaafd04 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -2,6 +2,7 @@ // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. // +using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; @@ -140,7 +141,7 @@ internal class NetEvents : IDisposable //CancellationTokenSource _waitForStartCancellationTokenSource; private readonly ManualResetEventSlim _winChange = new (false); - private readonly Queue _inputQueue = new (); + private readonly ConcurrentQueue _inputQueue = new (); private readonly ConsoleDriver _consoleDriver; private ConsoleKeyInfo [] _cki; private bool _isEscSeq; @@ -191,7 +192,10 @@ internal class NetEvents : IDisposable #endif if (_inputQueue.Count > 0) { - return _inputQueue.Dequeue (); + if (_inputQueue.TryDequeue (out InputResult? result)) + { + return result; + } } } @@ -200,17 +204,10 @@ internal class NetEvents : IDisposable private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true) { - // if there is a key available, return it without waiting - // (or dispatching work to the thread queue) - if (Console.KeyAvailable) - { - return Console.ReadKey (intercept); - } - while (!cancellationToken.IsCancellationRequested) { - Task.Delay (100, cancellationToken).Wait (cancellationToken); - + // if there is a key available, return it without waiting + // (or dispatching work to the thread queue) if (Console.KeyAvailable) { return Console.ReadKey (intercept); @@ -220,12 +217,15 @@ internal class NetEvents : IDisposable { if (_retries > 1) { - EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); - - lock (seqReqStatus.AnsiRequest._responseLock) + if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { - seqReqStatus.AnsiRequest.Response = string.Empty; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + lock (seqReqStatus!.AnsiRequest._responseLock) + { + EscSeqRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } } _retries = 0; @@ -239,6 +239,11 @@ internal class NetEvents : IDisposable { _retries = 0; } + + if (!_forceRead) + { + Task.Delay (100, cancellationToken).Wait (cancellationToken); + } } cancellationToken.ThrowIfCancellationRequested (); @@ -310,7 +315,10 @@ internal class NetEvents : IDisposable _isEscSeq = true; - if (_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) + || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))) { ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; @@ -510,7 +518,26 @@ internal class NetEvents : IDisposable return; } - HandleKeyboardEvent (newConsoleKeyInfo); + if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator)) + { + if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result)) + { + lock (result.AnsiRequest._responseLock) + { + result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator; + result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator); + + EscSeqUtils.InvalidRequestTerminator = null; + } + } + + return; + } + + if (newConsoleKeyInfo != default) + { + HandleKeyboardEvent (newConsoleKeyInfo); + } } [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] @@ -1822,7 +1849,7 @@ internal class NetMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - private readonly Queue _resultQueue = new (); + private readonly ConcurrentQueue _resultQueue = new (); internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private MainLoop _mainLoop; @@ -1897,11 +1924,12 @@ internal class NetMainLoop : IMainLoopDriver while (_resultQueue.Count > 0) { // Always dequeue even if it's null and invoke if isn't null - InputResult? dequeueResult = _resultQueue.Dequeue (); - - if (dequeueResult is { }) + if (_resultQueue.TryDequeue (out InputResult? dequeueResult)) { - ProcessInput?.Invoke (dequeueResult.Value); + if (dequeueResult is { }) + { + ProcessInput?.Invoke (dequeueResult.Value); + } } } } @@ -1960,10 +1988,10 @@ internal class NetMainLoop : IMainLoopDriver try { - while (_resultQueue.Count > 0 && _resultQueue.Peek () is null) + while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out InputResult? result) && result is null) { // Dequeue null values - _resultQueue.Dequeue (); + _resultQueue.TryDequeue (out _); } } catch (InvalidOperationException) // Peek can raise an exception diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 2c62de466..d6223dd96 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -8,13 +8,14 @@ // 2) The values provided during Init (and the first WindowsConsole.EventType.WindowBufferSize) are not correct. // // If HACK_CHECK_WINCHANGED is defined then we ignore WindowsConsole.EventType.WindowBufferSize events -// and instead check the console size every every 500ms in a thread in WidowsMainLoop. +// and instead check the console size every 500ms in a thread in WidowsMainLoop. // As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using // the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is // still incorrect so we still need this hack. #define HACK_CHECK_WINCHANGED +using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; @@ -920,6 +921,7 @@ internal class WindowsConsole uint numberEventsRead = 0; StringBuilder ansiSequence = new StringBuilder (); bool readingSequence = false; + bool raisedResponse = false; while (true) { @@ -972,6 +974,7 @@ internal class WindowsConsole lock (seqReqStatus!.AnsiRequest._responseLock) { + raisedResponse = true; seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); // Clear the terminator for not be enqueued @@ -985,16 +988,31 @@ internal class WindowsConsole } } - if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + if (readingSequence && !raisedResponse && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); + } + + _retries = 0; + } + else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) { if (_retries > 1) { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) + if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { - seqReqStatus.AnsiRequest.Response = string.Empty; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + lock (seqReqStatus!.AnsiRequest._responseLock) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } } _retries = 0; @@ -2337,7 +2355,7 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); // The records that we keep fetching - private readonly Queue _resultQueue = new (); + private readonly ConcurrentQueue _resultQueue = new (); internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly WindowsConsole _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); @@ -2422,11 +2440,12 @@ internal class WindowsMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { - WindowsConsole.InputRecord [] inputRecords = _resultQueue.Dequeue (); - - if (inputRecords is { Length: > 0 }) + if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord [] inputRecords)) { - ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]); + if (inputRecords is { Length: > 0 }) + { + ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]); + } } } #if HACK_CHECK_WINCHANGED @@ -2524,15 +2543,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - if (_eventReady.IsSet) - { - // it's already in an iteration and ensures set to iterate again - Application.Invoke (() => _eventReady.Set ()); - } - else - { - _eventReady.Set (); - } + _eventReady.Set (); } } From 81eb301b56e8bd57c24d46a8875378083fda94ca Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 20:25:04 +0000 Subject: [PATCH 068/151] Fix escSeqRequests that may be null running unit tests. --- Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 59c10b548..4b283afb1 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -364,7 +364,7 @@ public static class EscSeqUtils else { // It's request response that wasn't handled by a valid request terminator - System.Diagnostics.Debug.Assert (escSeqRequests is { Statuses.Count: > 0 }); + System.Diagnostics.Debug.Assert (escSeqRequests is null or { Statuses.Count: > 0 }); InvalidRequestTerminator = ToString (cki); } From 173f8205bef51366b4b338ea55c6cc4f1e6a14b6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Nov 2024 23:04:11 +0000 Subject: [PATCH 069/151] Disable HACK_CHECK_WINCHANGED. --- Terminal.Gui/Application/Application.Run.cs | 1 + Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 966fb04c6..e782934e0 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -493,6 +493,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) { if (tl.LayoutNeeded) { + tl.SetRelativeLayout (new (Driver!.Cols, Driver.Rows)); tl.LayoutSubviews (); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index d6223dd96..515ca001e 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -13,7 +13,7 @@ // the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is // still incorrect so we still need this hack. -#define HACK_CHECK_WINCHANGED +//#define HACK_CHECK_WINCHANGED using System.Collections.Concurrent; using System.ComponentModel; @@ -1708,15 +1708,17 @@ internal class WindowsDriver : ConsoleDriver break; #if !HACK_CHECK_WINCHANGED - case WindowsConsole.EventType.WindowBufferSize: - - Cols = inputEvent.WindowBufferSizeEvent._size.X; - Rows = inputEvent.WindowBufferSizeEvent._size.Y; + case WindowsConsole.EventType.WindowBufferSize: - ResizeScreen (); - ClearContents (); - TerminalResized.Invoke (); - break; + Cols = inputEvent.WindowBufferSizeEvent._size.X; + Rows = inputEvent.WindowBufferSizeEvent._size.Y; + + ResizeScreen (); + ClearContents (); + Application.Top?.SetNeedsLayout (); + Application.Refresh (); + + break; #endif } } From 472ec453f598b7030a4a39e1303be0960251adaf Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 5 Nov 2024 00:37:57 +0000 Subject: [PATCH 070/151] Remove _screenBuffer and only using the alternate buffer. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 311 ++++++++++--------- 1 file changed, 158 insertions(+), 153 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 515ca001e..d4c7bfc3a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -32,7 +32,7 @@ internal class WindowsConsole private readonly nint _inputHandle; private nint _outputHandle; - private nint _screenBuffer; + //private nint _screenBuffer; private readonly uint _originalConsoleMode; private CursorVisibility? _initialCursorVisibility; private CursorVisibility? _currentCursorVisibility; @@ -58,10 +58,10 @@ internal class WindowsConsole { //Debug.WriteLine ("WriteToConsole"); - if (_screenBuffer == nint.Zero) - { - ReadFromConsoleOutput (size, bufferSize, ref window); - } + //if (_screenBuffer == nint.Zero) + //{ + // ReadFromConsoleOutput (size, bufferSize, ref window); + //} var result = false; @@ -80,7 +80,7 @@ internal class WindowsConsole }; } - result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); + result = WriteConsoleOutput (_outputHandle, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); } else { @@ -125,7 +125,7 @@ internal class WindowsConsole if (s != _lastWrite) { // supply console with the new content - result = WriteConsole (_screenBuffer, s, (uint)s.Length, out uint _, nint.Zero); + result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero); } _lastWrite = s; @@ -133,7 +133,7 @@ internal class WindowsConsole foreach (var sixel in Application.Sixel) { SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y)); - WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } } @@ -143,7 +143,8 @@ internal class WindowsConsole if (err != 0) { - throw new Win32Exception (err); + //throw new Win32Exception (err); + // It's resizing } } @@ -152,10 +153,10 @@ internal class WindowsConsole internal bool WriteANSI (string ansi) { - if (WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, nint.Zero)) + if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero)) { // Flush the output to make sure it's sent immediately - return FlushFileBuffers (_screenBuffer); + return FlushFileBuffers (_outputHandle); } return false; @@ -163,34 +164,34 @@ internal class WindowsConsole public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) { - _screenBuffer = CreateConsoleScreenBuffer ( - DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - ShareMode.FileShareRead | ShareMode.FileShareWrite, - nint.Zero, - 1, - nint.Zero - ); + //_screenBuffer = CreateConsoleScreenBuffer ( + // DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + // ShareMode.FileShareRead | ShareMode.FileShareWrite, + // nint.Zero, + // 1, + // nint.Zero + // ); - if (_screenBuffer == INVALID_HANDLE_VALUE) - { - int err = Marshal.GetLastWin32Error (); + //if (_screenBuffer == INVALID_HANDLE_VALUE) + //{ + // int err = Marshal.GetLastWin32Error (); - if (err != 0) - { - throw new Win32Exception (err); - } - } + // if (err != 0) + // { + // throw new Win32Exception (err); + // } + //} SetInitialCursorVisibility (); - if (!SetConsoleActiveScreenBuffer (_screenBuffer)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + //if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + //{ + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + //} _originalStdOutChars = new CharInfo [size.Height * size.Width]; - if (!ReadConsoleOutput (_screenBuffer, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window)) + if (!ReadConsoleOutput (_outputHandle, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window)) { throw new Win32Exception (Marshal.GetLastWin32Error ()); } @@ -198,7 +199,7 @@ internal class WindowsConsole public bool SetCursorPosition (Coord position) { - return SetConsoleCursorPosition (_screenBuffer, position); + return SetConsoleCursorPosition (_outputHandle, position); } public void SetInitialCursorVisibility () @@ -211,14 +212,14 @@ internal class WindowsConsole public bool GetCursorVisibility (out CursorVisibility visibility) { - if (_screenBuffer == nint.Zero) + if (_outputHandle == nint.Zero) { visibility = CursorVisibility.Invisible; return false; } - if (!GetConsoleCursorInfo (_screenBuffer, out ConsoleCursorInfo info)) + if (!GetConsoleCursorInfo (_outputHandle, out ConsoleCursorInfo info)) { int err = Marshal.GetLastWin32Error (); @@ -286,7 +287,7 @@ internal class WindowsConsole bVisible = ((uint)visibility & 0xFF00) != 0 }; - if (!SetConsoleCursorInfo (_screenBuffer, ref info)) + if (!SetConsoleCursorInfo (_outputHandle, ref info)) { return false; } @@ -304,7 +305,7 @@ internal class WindowsConsole SetCursorVisibility (_initialCursorVisibility.Value); } - SetConsoleOutputWindow (out _); + //SetConsoleOutputWindow (out _); ConsoleMode = _originalConsoleMode; @@ -322,139 +323,139 @@ internal class WindowsConsole Console.WriteLine ("Error: {0}", err); } - if (_screenBuffer != nint.Zero) - { - CloseHandle (_screenBuffer); - } + //if (_screenBuffer != nint.Zero) + //{ + // CloseHandle (_screenBuffer); + //} - _screenBuffer = nint.Zero; + //_screenBuffer = nint.Zero; } - internal Size GetConsoleBufferWindow (out Point position) - { - if (_screenBuffer == nint.Zero) - { - position = Point.Empty; + //internal Size GetConsoleBufferWindow (out Point position) + //{ + // if (_screenBuffer == nint.Zero) + // { + // position = Point.Empty; - return Size.Empty; - } + // return Size.Empty; + // } - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - { - //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - position = Point.Empty; + // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + // position = Point.Empty; - return Size.Empty; - } + // return Size.Empty; + // } - Size sz = new ( - csbi.srWindow.Right - csbi.srWindow.Left + 1, - csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - position = new (csbi.srWindow.Left, csbi.srWindow.Top); + // Size sz = new ( + // csbi.srWindow.Right - csbi.srWindow.Left + 1, + // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + // position = new (csbi.srWindow.Left, csbi.srWindow.Top); - return sz; - } + // return sz; + //} - internal Size GetConsoleOutputWindow (out Point position) - { - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); + //internal Size GetConsoleOutputWindow (out Point position) + //{ + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + // if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } - Size sz = new ( - csbi.srWindow.Right - csbi.srWindow.Left + 1, - csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - position = new (csbi.srWindow.Left, csbi.srWindow.Top); + // Size sz = new ( + // csbi.srWindow.Right - csbi.srWindow.Left + 1, + // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + // position = new (csbi.srWindow.Left, csbi.srWindow.Top); - return sz; - } + // return sz; + //} - internal Size SetConsoleWindow (short cols, short rows) - { - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); + //internal Size SetConsoleWindow (short cols, short rows) + //{ + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } - Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer); - short newCols = Math.Min (cols, maxWinSize.X); - short newRows = Math.Min (rows, maxWinSize.Y); - csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); - csbi.srWindow = new SmallRect (0, 0, newCols, newRows); - csbi.dwMaximumWindowSize = new Coord (newCols, newRows); + // Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer); + // short newCols = Math.Min (cols, maxWinSize.X); + // short newRows = Math.Min (rows, maxWinSize.Y); + // csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); + // csbi.srWindow = new SmallRect (0, 0, newCols, newRows); + // csbi.dwMaximumWindowSize = new Coord (newCols, newRows); - if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + // if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } - var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); + // var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); - if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) - { - //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - return new (cols, rows); - } + // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + // { + // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + // return new (cols, rows); + // } - SetConsoleOutputWindow (csbi); + // SetConsoleOutputWindow (csbi); - return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); - } + // return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); + //} - private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) - { - if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } - } + //private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) + //{ + // if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + //} - internal Size SetConsoleOutputWindow (out Point position) - { - if (_screenBuffer == nint.Zero) - { - position = Point.Empty; + //internal Size SetConsoleOutputWindow (out Point position) + //{ + // if (_screenBuffer == nint.Zero) + // { + // position = Point.Empty; - return Size.Empty; - } + // return Size.Empty; + // } - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } - Size sz = new ( - csbi.srWindow.Right - csbi.srWindow.Left + 1, - Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); - position = new (csbi.srWindow.Left, csbi.srWindow.Top); - SetConsoleOutputWindow (csbi); - var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); + // Size sz = new ( + // csbi.srWindow.Right - csbi.srWindow.Left + 1, + // Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); + // position = new (csbi.srWindow.Left, csbi.srWindow.Top); + // SetConsoleOutputWindow (csbi); + // var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); - if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + // if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } - if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } - return sz; - } + // return sz; + //} private uint ConsoleMode { @@ -1487,12 +1488,12 @@ internal class WindowsDriver : ConsoleDriver public override void UpdateScreen () { - Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows); + //Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows); - if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) - { - return; - } + //if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) + //{ + // return; + //} var bufferCoords = new WindowsConsole.Coord { @@ -1561,7 +1562,8 @@ internal class WindowsDriver : ConsoleDriver if (err != 0) { - throw new Win32Exception (err); + //throw new Win32Exception (err); + // It's resizing } } @@ -1598,14 +1600,17 @@ internal class WindowsDriver : ConsoleDriver { try { - if (WinConsole is { }) - { - // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. - // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED - Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); - Cols = winSize.Width; - Rows = winSize.Height; - } + //if (WinConsole is { }) + //{ + // // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. + // // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED + // Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); + // Cols = winSize.Width; + // Rows = winSize.Height; + //} + + Cols = Console.WindowWidth; + Rows = Console.WindowHeight; WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); From ad4f6c49e12b7971a5bb3a9b6ef61d87a1522165 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 5 Nov 2024 00:49:33 +0000 Subject: [PATCH 071/151] Fix unit test. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index d4c7bfc3a..4bcad4b21 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1648,7 +1648,11 @@ internal class WindowsDriver : ConsoleDriver _mainLoopDriver.WinChanged = ChangeWin; #endif - WinConsole?.SetInitialCursorVisibility (); + if (!RunningUnitTests) + { + WinConsole?.SetInitialCursorVisibility (); + } + return new MainLoop (_mainLoopDriver); } From f73c8173ba8c7f944dfdad5ad053b087df0b87fc Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 5 Nov 2024 01:25:40 +0000 Subject: [PATCH 072/151] Fix unit tests. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 72 ++++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 4bcad4b21..1ee0e8023 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -143,8 +143,7 @@ internal class WindowsConsole if (err != 0) { - //throw new Win32Exception (err); - // It's resizing + throw new Win32Exception (err); } } @@ -359,23 +358,23 @@ internal class WindowsConsole // return sz; //} - //internal Size GetConsoleOutputWindow (out Point position) - //{ - // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - // csbi.cbSize = (uint)Marshal.SizeOf (csbi); + internal Size GetConsoleOutputWindow (out Point position) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); - // if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } + if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } - // Size sz = new ( - // csbi.srWindow.Right - csbi.srWindow.Left + 1, - // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - // position = new (csbi.srWindow.Left, csbi.srWindow.Top); + Size sz = new ( + csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new (csbi.srWindow.Left, csbi.srWindow.Top); - // return sz; - //} + return sz; + } //internal Size SetConsoleWindow (short cols, short rows) //{ @@ -1223,9 +1222,12 @@ internal class WindowsDriver : ConsoleDriver public override void Refresh () { - UpdateScreen (); - //WinConsole?.SetInitialCursorVisibility (); - UpdateCursor (); + if (!RunningUnitTests) + { + UpdateScreen (); + //WinConsole?.SetInitialCursorVisibility (); + UpdateCursor (); + } } public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) @@ -1488,12 +1490,12 @@ internal class WindowsDriver : ConsoleDriver public override void UpdateScreen () { - //Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows); + Size windowSize = WinConsole?.GetConsoleOutputWindow (out Point _) ?? new Size (Cols, Rows); - //if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) - //{ - // return; - //} + if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) + { + return; + } var bufferCoords = new WindowsConsole.Coord { @@ -1562,8 +1564,7 @@ internal class WindowsDriver : ConsoleDriver if (err != 0) { - //throw new Win32Exception (err); - // It's resizing + throw new Win32Exception (err); } } @@ -1600,17 +1601,14 @@ internal class WindowsDriver : ConsoleDriver { try { - //if (WinConsole is { }) - //{ - // // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. - // // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED - // Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); - // Cols = winSize.Width; - // Rows = winSize.Height; - //} - - Cols = Console.WindowWidth; - Rows = Console.WindowHeight; + if (WinConsole is { }) + { + // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. + // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED + Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); + Cols = winSize.Width; + Rows = winSize.Height; + } WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); From 7921ae35f744af36065d057877e97d61e9924101 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 5 Nov 2024 17:55:29 +0000 Subject: [PATCH 073/151] Returns ansiRequest.Response instead of a variable for more thread safe. --- .../CursesDriver/CursesDriver.cs | 19 ++++++++++--------- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 19 ++++++++++--------- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 19 ++++++++++--------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 1f4f2bc3f..1086bd6e3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -217,8 +217,6 @@ internal class CursesDriver : ConsoleDriver return string.Empty; } - var response = string.Empty; - try { lock (ansiRequest._responseLock) @@ -226,8 +224,7 @@ internal class CursesDriver : ConsoleDriver ansiRequest.ResponseFromInput += (s, e) => { Debug.Assert (s == ansiRequest); - - ansiRequest.Response = response = e; + Debug.Assert (e == ansiRequest.Response); _waitAnsiResponse.Set (); }; @@ -248,7 +245,8 @@ internal class CursesDriver : ConsoleDriver { return string.Empty; } - finally + + lock (ansiRequest._responseLock) { _mainLoopDriver._forceRead = false; @@ -257,15 +255,18 @@ internal class CursesDriver : ConsoleDriver if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) { - // Bad request or no response at all - _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + lock (request!.AnsiRequest._responseLock) + { + // Bad request or no response at all + _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + } } } _waitAnsiResponse.Reset (); - } - return response; + return ansiRequest.Response; + } } /// diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 3afaafd04..afa78ff6a 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1502,8 +1502,6 @@ internal class NetDriver : ConsoleDriver return string.Empty; } - var response = string.Empty; - try { lock (ansiRequest._responseLock) @@ -1511,8 +1509,7 @@ internal class NetDriver : ConsoleDriver ansiRequest.ResponseFromInput += (s, e) => { Debug.Assert (s == ansiRequest); - - ansiRequest.Response = response = e; + Debug.Assert (e == ansiRequest.Response); _waitAnsiResponse.Set (); }; @@ -1538,7 +1535,8 @@ internal class NetDriver : ConsoleDriver { return string.Empty; } - finally + + lock (ansiRequest._responseLock) { _mainLoopDriver._netEvents._forceRead = false; @@ -1547,15 +1545,18 @@ internal class NetDriver : ConsoleDriver if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) { - // Bad request or no response at all - _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); + lock (request!.AnsiRequest._responseLock) + { + // Bad request or no response at all + _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); + } } } _waitAnsiResponse.Reset (); - } - return response; + return ansiRequest.Response; + } } /// diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 1ee0e8023..83763e081 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1311,8 +1311,6 @@ internal class WindowsDriver : ConsoleDriver return string.Empty; } - var response = string.Empty; - try { lock (ansiRequest._responseLock) @@ -1320,8 +1318,7 @@ internal class WindowsDriver : ConsoleDriver ansiRequest.ResponseFromInput += (s, e) => { Debug.Assert (s == ansiRequest); - - ansiRequest.Response = response = e; + Debug.Assert (e == ansiRequest.Response); _waitAnsiResponse.Set (); }; @@ -1342,7 +1339,8 @@ internal class WindowsDriver : ConsoleDriver { return string.Empty; } - finally + + lock (ansiRequest._responseLock) { _mainLoopDriver._forceRead = false; @@ -1351,15 +1349,18 @@ internal class WindowsDriver : ConsoleDriver if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) { - // Bad request or no response at all - _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + lock (request!.AnsiRequest._responseLock) + { + // Bad request or no response at all + _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + } } } _waitAnsiResponse.Reset (); - } - return response; + return ansiRequest.Response; + } } public override void WriteRaw (string ansi) From ecbbed0f6dfeed36954fe8b7f67173f15c047518 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 5 Nov 2024 18:34:58 +0000 Subject: [PATCH 074/151] For re-run CI. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 1086bd6e3..4568bcf46 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -209,7 +209,7 @@ internal class CursesDriver : ConsoleDriver private readonly ManualResetEventSlim _waitAnsiResponse = new (false); private readonly CancellationTokenSource _ansiResponseTokenSource = new (); - /// + /// public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { if (_mainLoopDriver is null) From 2919d55817972112f17118346252be7583b3963c Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 6 Nov 2024 11:33:02 -0700 Subject: [PATCH 075/151] Code Review --- .../AnsiEscapeSequenceRequest.cs | 38 +- .../AnsiEscapeSequenceResponse.cs | 27 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 665 ++---- .../ConsoleDrivers/ConsoleKeyMapping.cs | 1 + .../CursesDriver/CursesDriver.cs | 1057 ++++----- .../CursesDriver/GetTIOCGWINSZ.c | 9 +- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 15 +- .../EscSeqUtils/EscSeqReqStatus.cs | 17 + .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 14 +- Terminal.Gui/ConsoleDrivers/KeyCode.cs | 321 +++ Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2007 ----------------- .../ConsoleDrivers/NetDriver/NetDriver.cs | 965 ++++++++ .../ConsoleDrivers/NetDriver/NetEvents.cs | 753 +++++++ .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 173 ++ .../NetDriver/NetWinVTConsole.cs | 125 + .../WindowsDriver/WindowsConsole.cs | 1109 +++++++++ .../{ => WindowsDriver}/WindowsDriver.cs | 1106 +-------- Terminal.Gui/Terminal.Gui.csproj | 3 +- .../Scenarios/AnsiEscapeSequenceRequests.cs | 194 +- 19 files changed, 4348 insertions(+), 4251 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs create mode 100644 Terminal.Gui/ConsoleDrivers/KeyCode.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/NetDriver.cs create mode 100644 Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs create mode 100644 Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs create mode 100644 Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs create mode 100644 Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs create mode 100644 Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs rename Terminal.Gui/ConsoleDrivers/{ => WindowsDriver}/WindowsDriver.cs (60%) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 662736e93..2e8da6bfe 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -4,35 +4,38 @@ namespace Terminal.Gui; /// /// Describes an ongoing ANSI request sent to the console. /// Use to handle the response -/// when console answers the request. +/// when the console answers the request. /// public class AnsiEscapeSequenceRequest { internal readonly object _responseLock = new (); // Per-instance lock /// - /// Request to send e.g. see + /// Gets the request string to send e.g. see /// /// EscSeqUtils.CSI_SendDeviceAttributes.Request /// /// public required string Request { get; init; } + // QUESTION: Could the type of this propperty be AnsiEscapeSequenceResponse? This would remove the + // QUESTION: removal of the redundant Rresponse, Terminator, and ExpectedRespnseValue properties from this class? + // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable? /// - /// Response received from the request. + /// Gets the response received from the request. /// public string Response { get; internal set; } = string.Empty; /// - /// Invoked when the console responds with an ANSI response code that matches the + /// Raised when the console responds with an ANSI response code that matches the /// /// public event EventHandler? ResponseReceived; /// /// - /// The terminator that uniquely identifies the type of response as responded - /// by the console. e.g. for + /// Gets the terminator that uniquely identifies the response received from + /// the console. e.g. for /// /// EscSeqUtils.CSI_SendDeviceAttributes.Request /// @@ -50,15 +53,15 @@ public class AnsiEscapeSequenceRequest public required string Terminator { get; init; } /// - /// Execute an ANSI escape sequence escape which may return a response or error. + /// Attempt an ANSI escape sequence request which may return a response or error. /// /// The ANSI escape sequence to request. /// - /// When this method returns , an object containing the response with an empty - /// error. + /// When this method returns , the response. will + /// be . /// - /// A with the response, error, terminator and value. - public static bool TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) + /// A with the response, error, terminator, and value. + public static bool TryRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) { var error = new StringBuilder (); var values = new string? [] { null }; @@ -72,7 +75,7 @@ public class AnsiEscapeSequenceRequest if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (EscSeqUtils.KeyEsc)) { - throw new InvalidOperationException ("Invalid escape character!"); + throw new InvalidOperationException ($"Invalid Response: {ansiRequest.Response}"); } if (string.IsNullOrEmpty (ansiRequest.Terminator)) @@ -102,7 +105,7 @@ public class AnsiEscapeSequenceRequest AnsiEscapeSequenceResponse ansiResponse = new () { Response = ansiRequest.Response, Error = error.ToString (), - Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), Value = values [0] + Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), ExpectedResponseValue = values [0] }; // Invoke the event if it's subscribed @@ -114,16 +117,17 @@ public class AnsiEscapeSequenceRequest } /// - /// The value expected in the response e.g. + /// The value expected in the response after the CSI e.g. /// /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value /// - /// which will have a 't' as terminator but also other different request may return the same terminator with a - /// different value. + /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, + /// will be "8". /// - public string? Value { get; init; } + public string? ExpectedResponseValue { get; init; } internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string response) { ResponseFromInput?.Invoke (ansiRequest, response); } + // QUESTION: What is this for? Please provide a descriptive comment. internal event EventHandler? ResponseFromInput; } diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs index df9851155..2cc820801 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs @@ -2,20 +2,23 @@ namespace Terminal.Gui; /// -/// Describes a finished ANSI received from the console. +/// Describes a response received from the console as a result of a request being sent via . /// public class AnsiEscapeSequenceResponse { + // QUESTION: Should this be nullable to indicate there was no error, or is string.Empty sufficient? /// - /// Error received from e.g. see + /// Gets the error string received from e.g. see /// /// EscSeqUtils.CSI_SendDeviceAttributes.Request /// + /// . /// public required string Error { get; init; } + // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable? /// - /// Response received from e.g. see + /// Gets the Response string received from e.g. see /// /// EscSeqUtils.CSI_SendDeviceAttributes.Request /// @@ -23,10 +26,11 @@ public class AnsiEscapeSequenceResponse /// public required string Response { get; init; } + // QUESTION: Does string.Empty indicate no terminator expected? If not, perhaps make this property nullable? /// /// - /// The terminator that uniquely identifies the type of response as responded - /// by the console. e.g. for + /// Gets the terminator that uniquely identifies the response received from + /// the console. e.g. for /// /// EscSeqUtils.CSI_SendDeviceAttributes.Request /// @@ -34,20 +38,23 @@ public class AnsiEscapeSequenceResponse /// /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator /// + /// . /// /// - /// The received terminator must match to the terminator sent by the request. + /// After sending a request, the first response with matching terminator will be matched + /// to the oldest outstanding request. /// /// public required string Terminator { get; init; } /// - /// The value expected in the response e.g. + /// The value expected in the response after the CSI e.g. /// /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value /// - /// which will have a 't' as terminator but also other different request may return the same terminator with a - /// different value. + /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, + /// will be "8". /// - public string? Value { get; init; } + + public string? ExpectedResponseValue { get; init; } } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index b37c50c53..d6ccf9991 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1,7 +1,4 @@ #nullable enable -// -// ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations. -// using System.Diagnostics; @@ -15,6 +12,53 @@ namespace Terminal.Gui; /// public abstract class ConsoleDriver { + /// + /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. + /// + /// public ColorTests () + /// { + /// ConsoleDriver.RunningUnitTests = true; + /// } + /// + /// + internal static bool RunningUnitTests { get; set; } + + /// Get the operating system clipboard. + public IClipboard? Clipboard { get; internal set; } + + /// Returns the name of the driver and relevant library version information. + /// + public virtual string GetVersionInfo () { return GetType ().Name; } + + /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. + /// This is only implemented in . + public abstract void Suspend (); + + #region ANSI Esc Sequence Handling + + // QUESTION: Should this be virtual with a default implementation that does the common stuff? + // QUESTION: Looking at the implementations of this method, there is TONs of duplicated code. + // QUESTION: We should figure out how to find just the things that are unique to each driver and + // QUESTION: create more fine-grained APIs to handle those. + /// + /// Provide handling for the terminal write ANSI escape sequence request. + /// + /// The object. + /// The request response. + public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); + + // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. + // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API? + /// + /// Provide proper writing to send escape sequence recognized by the . + /// + /// + public abstract void WriteRaw (string ansi); + + #endregion ANSI Esc Sequence Handling + + #region Screen and Contents + // As performance is a concern, we keep track of the dirty lines and only refresh those. // This is in addition to the dirty flag on each cell. internal bool []? _dirtyLines; @@ -23,30 +67,18 @@ public abstract class ConsoleDriver /// Gets the location and size of the terminal screen. internal Rectangle Screen => new (0, 0, Cols, Rows); - private Rectangle _clip; + /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. + public abstract void UpdateScreen (); - /// - /// Gets or sets the clip rectangle that and are subject - /// to. - /// - /// The rectangle describing the of region. - public Rectangle Clip - { - get => _clip; - set - { - if (_clip == value) - { - return; - } + /// Called when the terminal size changes. Fires the event. + /// + public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); } - // Don't ever let Clip be bigger than Screen - _clip = Rectangle.Intersect (Screen, value); - } - } + /// The event fired when the terminal is resized. + public event EventHandler? SizeChanged; - /// Get the operating system clipboard. - public IClipboard? Clipboard { get; internal set; } + /// Updates the screen to reflect all the changes that have been done to the display buffer + public abstract void Refresh (); /// /// Gets the column last set by . and are used by @@ -75,6 +107,43 @@ public abstract class ConsoleDriver /// The leftmost column in the terminal. public virtual int Left { get; internal set; } = 0; + /// 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 virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); } + + /// Tests whether the specified coordinate are valid for drawing. + /// The column. + /// The row. + /// + /// if the coordinate is outside the screen bounds or outside of . + /// otherwise. + /// + public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (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 virtual void Move (int col, int row) + { + //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0)); + Col = col; + Row = row; + } + /// /// Gets the row last set by . and are used by /// and to determine where to add content. @@ -95,16 +164,27 @@ public abstract class ConsoleDriver /// The topmost row in the terminal. public virtual int Top { get; internal set; } = 0; + private Rectangle _clip; + /// - /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. - /// - /// public ColorTests () - /// { - /// ConsoleDriver.RunningUnitTests = true; - /// } - /// + /// Gets or sets the clip rectangle that and are subject + /// to. /// - internal static bool RunningUnitTests { get; set; } + /// The rectangle describing the of region. + public Rectangle Clip + { + get => _clip; + set + { + if (_clip == value) + { + return; + } + + // Don't ever let Clip be bigger than Screen + _clip = Rectangle.Intersect (Screen, value); + } + } /// Adds the specified rune to the display at the current cursor position. /// @@ -310,10 +390,38 @@ public abstract class ConsoleDriver } } + /// 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) + { + rect = Rectangle.Intersect (rect, Clip); + + lock (Contents!) + { + for (int r = rect.Y; r < rect.Y + rect.Height; r++) + { + for (int c = rect.X; c < rect.X + rect.Width; c++) + { + Contents [r, c] = new () + { + Rune = rune != default (Rune) ? rune : (Rune)' ', + Attribute = CurrentAttribute, IsDirty = true + }; + _dirtyLines! [r] = true; + } + } + } + } + /// Clears the of the driver. public void ClearContents () { Contents = new Cell [Rows, Cols]; + //CONCURRENCY: Unsynchronized access to Clip isn't safe. // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere. Clip = Screen; @@ -325,21 +433,22 @@ public abstract class ConsoleDriver { for (var c = 0; c < Cols; c++) { - Contents [row, c] = new Cell + Contents [row, c] = new () { Rune = (Rune)' ', Attribute = new Attribute (Color.White, Color.Black), IsDirty = true }; } + _dirtyLines [row] = true; } } } /// - /// Sets as dirty for situations where views - /// don't need layout and redrawing, but just refresh the screen. + /// Sets as dirty for situations where views + /// don't need layout and redrawing, but just refresh the screen. /// public void SetContentsAsDirty () { @@ -351,41 +460,12 @@ public abstract class ConsoleDriver { Contents [row, c].IsDirty = true; } + _dirtyLines! [row] = true; } } } - /// Determines if the terminal cursor should be visible or not and sets it accordingly. - /// upon success - public abstract bool EnsureCursorVisibility (); - - /// 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) - { - rect = Rectangle.Intersect (rect, Clip); - lock (Contents!) - { - for (int r = rect.Y; r < rect.Y + rect.Height; r++) - { - for (int c = rect.X; c < rect.X + rect.Width; c++) - { - Contents [r, c] = new Cell - { - Rune = rune != default ? rune : (Rune)' ', - Attribute = CurrentAttribute, IsDirty = true - }; - _dirtyLines! [r] = true; - } - } - } - } - /// /// Fills the specified rectangle with the specified . This method is a convenience method /// that calls . @@ -394,79 +474,28 @@ public abstract class ConsoleDriver /// public void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); } + #endregion Screen and Contents + + #region Cursor Handling + + /// Determines if the terminal cursor should be visible or not and sets it accordingly. + /// upon success + public abstract bool EnsureCursorVisibility (); + /// Gets the terminal cursor visibility. /// The current /// upon success public abstract bool GetCursorVisibility (out CursorVisibility visibility); - /// Returns the name of the driver and relevant library version information. - /// - public virtual string GetVersionInfo () { return GetType ().Name; } - - /// 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 virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); } - - /// Tests whether the specified coordinate are valid for drawing. - /// The column. - /// The row. - /// - /// if the coordinate is outside the screen bounds or outside of . - /// otherwise. - /// - public bool IsValidLocation (int col, int row) - { - return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (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 virtual void Move (int col, int row) - { - //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0)); - Col = col; - Row = row; - } - - /// Called when the terminal size changes. Fires the event. - /// - public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); } - - /// Updates the screen to reflect all the changes that have been done to the display buffer - public abstract void Refresh (); + /// Sets the position of the terminal cursor to and . + public abstract void UpdateCursor (); /// Sets the terminal cursor visibility. /// The wished /// upon success public abstract bool SetCursorVisibility (CursorVisibility visibility); - /// The event fired when the terminal is resized. - public event EventHandler? SizeChanged; - - /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. - /// This is only implemented in . - public abstract void Suspend (); - - /// Sets the position of the terminal cursor to and . - public abstract void UpdateCursor (); - - /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. - public abstract void UpdateScreen (); + #endregion Cursor Handling #region Setup & Teardown @@ -518,7 +547,7 @@ public abstract class ConsoleDriver // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed. if (Application.Driver is { }) { - _currentAttribute = new Attribute (value.Foreground, value.Background); + _currentAttribute = new (value.Foreground, value.Background); return; } @@ -551,16 +580,33 @@ public abstract class ConsoleDriver public virtual Attribute MakeColor (in Color foreground, in Color background) { // Encode the colors into the int value. - return new Attribute ( - -1, // only used by cursesdriver! - foreground, - background - ); + return new ( + -1, // only used by cursesdriver! + foreground, + background + ); } - #endregion + #endregion Color Handling - #region Mouse and Keyboard + #region Mouse Handling + + /// Event fired when a mouse event occurs. + public event EventHandler? MouseEvent; + + /// Called when a mouse event occurs. Fires the event. + /// + public void OnMouseEvent (MouseEventArgs a) + { + // Ensure ScreenPosition is set + a.ScreenPosition = a.Position; + + MouseEvent?.Invoke (this, a); + } + + #endregion Mouse Handling + + #region Keyboard Handling /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; @@ -587,19 +633,8 @@ public abstract class ConsoleDriver /// public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); } - /// Event fired when a mouse event occurs. - public event EventHandler? MouseEvent; - - /// Called when a mouse event occurs. Fires the event. - /// - public void OnMouseEvent (MouseEventArgs a) - { - // Ensure ScreenPosition is set - a.ScreenPosition = a.Position; - - MouseEvent?.Invoke (this, a); - } - + // TODO: Remove this API - it was needed when we didn't have a reliable way to simulate key presses. + // TODO: We now do: Applicaiton.RaiseKeyDown and Application.RaiseKeyUp /// Simulates a key press. /// The key character. /// The key. @@ -608,337 +643,5 @@ public abstract class ConsoleDriver /// If simulates the Ctrl key being pressed. public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); - /// - /// Provide handling for the terminal write ANSI escape sequence request. - /// - /// The object. - /// The request response. - public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); - - /// - /// Provide proper writing to send escape sequence recognized by the . - /// - /// - public abstract void WriteRaw (string ansi); - - #endregion -} - -/// -/// The enumeration encodes key information from s and provides a -/// consistent way for application code to specify keys and receive key events. -/// -/// The class provides a higher-level abstraction, with helper methods and properties for -/// common operations. For example, and provide a convenient way -/// to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed. -/// -/// -/// -/// -/// Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a -/// keyboard. Enum values are provided for these (e.g. , , etc.). -/// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent -/// *lowercase*, un-shifted characters. -/// -/// -/// Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. , -/// , etc.). -/// -/// -/// The shift modifiers (, , and -/// ) can be combined (with logical or) with the other key codes to represent shifted -/// keys. For example, the enum value represents the un-shifted 'a' key, while -/// | represents the 'A' key (shifted 'a' key). Likewise, -/// | represents the 'Alt+A' key combination. -/// -/// -/// All other keys that produce a printable character are encoded as the Unicode value of the character. For -/// example, the for the '!' character is 33, which is the Unicode value for '!'. Likewise, -/// `â` is 226, `Â` is 194, etc. -/// -/// -/// If the is set, then the value is that of the special mask, otherwise, the value is -/// the one of the lower bits (as extracted by ). -/// -/// -[Flags] -public enum KeyCode : uint -{ - /// - /// Mask that indicates that the key is a unicode codepoint. Values outside this range indicate the key has shift - /// modifiers or is a special key like function keys, arrows keys and so on. - /// - CharMask = 0x_f_ffff, - - /// - /// If the is set, then the value is that of the special mask, otherwise, the value is - /// in the lower bits (as extracted by ). - /// - SpecialMask = 0x_fff0_0000, - - /// - /// When this value is set, the Key encodes the sequence Shift-KeyValue. The actual value must be extracted by - /// removing the ShiftMask. - /// - ShiftMask = 0x_1000_0000, - - /// - /// When this value is set, the Key encodes the sequence Alt-KeyValue. The actual value must be extracted by - /// removing the AltMask. - /// - AltMask = 0x_8000_0000, - - /// - /// When this value is set, the Key encodes the sequence Ctrl-KeyValue. The actual value must be extracted by - /// removing the CtrlMask. - /// - CtrlMask = 0x_4000_0000, - - /// The key code representing an invalid or empty key. - Null = 0, - - /// Backspace key. - Backspace = 8, - - /// The key code for the tab key (forwards tab key). - Tab = 9, - - /// The key code for the return key. - Enter = ConsoleKey.Enter, - - /// The key code for the clear key. - Clear = 12, - - /// The key code for the escape key. - Esc = 27, - - /// The key code for the space bar key. - Space = 32, - - /// Digit 0. - D0 = 48, - - /// Digit 1. - D1, - - /// Digit 2. - D2, - - /// Digit 3. - D3, - - /// Digit 4. - D4, - - /// Digit 5. - D5, - - /// Digit 6. - D6, - - /// Digit 7. - D7, - - /// Digit 8. - D8, - - /// Digit 9. - D9, - - /// The key code for the A key - A = 65, - - /// The key code for the B key - B, - - /// The key code for the C key - C, - - /// The key code for the D key - D, - - /// The key code for the E key - E, - - /// The key code for the F key - F, - - /// The key code for the G key - G, - - /// The key code for the H key - H, - - /// The key code for the I key - I, - - /// The key code for the J key - J, - - /// The key code for the K key - K, - - /// The key code for the L key - L, - - /// The key code for the M key - M, - - /// The key code for the N key - N, - - /// The key code for the O key - O, - - /// The key code for the P key - P, - - /// The key code for the Q key - Q, - - /// The key code for the R key - R, - - /// The key code for the S key - S, - - /// The key code for the T key - T, - - /// The key code for the U key - U, - - /// The key code for the V key - V, - - /// The key code for the W key - W, - - /// The key code for the X key - X, - - /// The key code for the Y key - Y, - - /// The key code for the Z key - Z, - - ///// - ///// The key code for the Delete key. - ///// - //Delete = 127, - - // --- Special keys --- - // The values below are common non-alphanum keys. Their values are - // based on the .NET ConsoleKey values, which, in-turn are based on the - // VK_ values from the Windows API. - // We add MaxCodePoint to avoid conflicts with the Unicode values. - - /// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys. - MaxCodePoint = 0x10FFFF, - - /// Cursor up key - CursorUp = MaxCodePoint + ConsoleKey.UpArrow, - - /// Cursor down key. - CursorDown = MaxCodePoint + ConsoleKey.DownArrow, - - /// Cursor left key. - CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow, - - /// Cursor right key. - CursorRight = MaxCodePoint + ConsoleKey.RightArrow, - - /// Page Up key. - PageUp = MaxCodePoint + ConsoleKey.PageUp, - - /// Page Down key. - PageDown = MaxCodePoint + ConsoleKey.PageDown, - - /// Home key. - Home = MaxCodePoint + ConsoleKey.Home, - - /// End key. - End = MaxCodePoint + ConsoleKey.End, - - /// Insert (INS) key. - Insert = MaxCodePoint + ConsoleKey.Insert, - - /// Delete (DEL) key. - Delete = MaxCodePoint + ConsoleKey.Delete, - - /// Print screen character key. - PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen, - - /// F1 key. - F1 = MaxCodePoint + ConsoleKey.F1, - - /// F2 key. - F2 = MaxCodePoint + ConsoleKey.F2, - - /// F3 key. - F3 = MaxCodePoint + ConsoleKey.F3, - - /// F4 key. - F4 = MaxCodePoint + ConsoleKey.F4, - - /// F5 key. - F5 = MaxCodePoint + ConsoleKey.F5, - - /// F6 key. - F6 = MaxCodePoint + ConsoleKey.F6, - - /// F7 key. - F7 = MaxCodePoint + ConsoleKey.F7, - - /// F8 key. - F8 = MaxCodePoint + ConsoleKey.F8, - - /// F9 key. - F9 = MaxCodePoint + ConsoleKey.F9, - - /// F10 key. - F10 = MaxCodePoint + ConsoleKey.F10, - - /// F11 key. - F11 = MaxCodePoint + ConsoleKey.F11, - - /// F12 key. - F12 = MaxCodePoint + ConsoleKey.F12, - - /// F13 key. - F13 = MaxCodePoint + ConsoleKey.F13, - - /// F14 key. - F14 = MaxCodePoint + ConsoleKey.F14, - - /// F15 key. - F15 = MaxCodePoint + ConsoleKey.F15, - - /// F16 key. - F16 = MaxCodePoint + ConsoleKey.F16, - - /// F17 key. - F17 = MaxCodePoint + ConsoleKey.F17, - - /// F18 key. - F18 = MaxCodePoint + ConsoleKey.F18, - - /// F19 key. - F19 = MaxCodePoint + ConsoleKey.F19, - - /// F20 key. - F20 = MaxCodePoint + ConsoleKey.F20, - - /// F21 key. - F21 = MaxCodePoint + ConsoleKey.F21, - - /// F22 key. - F22 = MaxCodePoint + ConsoleKey.F22, - - /// F23 key. - F23 = MaxCodePoint + ConsoleKey.F23, - - /// F24 key. - F24 = MaxCodePoint + ConsoleKey.F24 + #endregion Keyboard Handling } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index 503e7cd57..6fce2e040 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; namespace Terminal.Gui.ConsoleDrivers; +// QUESTION: This class combines Windows specific code with cross-platform code. Should this be split into two classes? /// Helper class to handle the scan code and virtual key from a . public static class ConsoleKeyMapping { diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 4568bcf46..e0225b3aa 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -1,4 +1,5 @@ -// +// TODO: #nullable enable +// // Driver.cs: Curses-based Driver // @@ -9,14 +10,36 @@ using Unix.Terminal; namespace Terminal.Gui; -/// This is the Curses driver for the gui.cs/Terminal framework. +/// A Linux/Mac driver based on the Curses libary. internal class CursesDriver : ConsoleDriver { - public Curses.Window _window; - private CursorVisibility? _currentCursorVisibility; - private CursorVisibility? _initialCursorVisibility; - private MouseFlags _lastMouseFlags; - private UnixMainLoop _mainLoopDriver; + public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; } + + public override void Refresh () + { + UpdateScreen (); + UpdateCursor (); + } + + public override void Suspend () + { + StopReportingMouseMoves (); + + if (!RunningUnitTests) + { + Platform.Suspend (); + + if (Force16Colors) + { + Curses.Window.Standard.redrawwin (); + Curses.refresh (); + } + } + + StartReportingMouseMoves (); + } + + #region Screen and Contents public override int Cols { @@ -38,59 +61,6 @@ internal class CursesDriver : ConsoleDriver } } - public override bool SupportsTrueColor => true; - - /// - public override bool EnsureCursorVisibility () - { - if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) - { - GetCursorVisibility (out CursorVisibility cursorVisibility); - _currentCursorVisibility = cursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - - return false; - } - - SetCursorVisibility (_currentCursorVisibility ?? CursorVisibility.Default); - - return _currentCursorVisibility == CursorVisibility.Default; - } - - /// - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - visibility = CursorVisibility.Invisible; - - if (!_currentCursorVisibility.HasValue) - { - return false; - } - - visibility = _currentCursorVisibility.Value; - - return true; - } - - public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; } - - public static bool Is_WSL_Platform () - { - // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell - //if (new CursesClipboard ().IsSupported) { - // // If xclip is installed on Linux under WSL, this will return true. - // return false; - //} - (int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true); - - if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) - { - return true; - } - - return false; - } - public override bool IsRuneSupported (Rune rune) { // See Issue #2615 - CursesDriver is broken with non-BMP characters @@ -118,202 +88,6 @@ internal class CursesDriver : ConsoleDriver } } - public override void Refresh () - { - UpdateScreen (); - UpdateCursor (); - } - - public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) - { - KeyCode key; - - if (consoleKey == ConsoleKey.Packet) - { - var mod = new ConsoleModifiers (); - - if (shift) - { - mod |= ConsoleModifiers.Shift; - } - - if (alt) - { - mod |= ConsoleModifiers.Alt; - } - - if (control) - { - mod |= ConsoleModifiers.Control; - } - - var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); - key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo); - } - else - { - key = (KeyCode)keyChar; - } - - OnKeyDown (new Key (key)); - OnKeyUp (new Key (key)); - - //OnKeyPressed (new KeyEventArgsEventArgs (key)); - } - - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - if (_initialCursorVisibility.HasValue == false) - { - return false; - } - - if (!RunningUnitTests) - { - Curses.curs_set (((int)visibility >> 16) & 0x000000FF); - } - - if (visibility != CursorVisibility.Invisible) - { - Console.Out.Write ( - EscSeqUtils.CSI_SetCursorStyle ( - (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) - & 0xFF) - ) - ); - } - - _currentCursorVisibility = visibility; - - return true; - } - - public void StartReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); - } - } - - public void StopReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); - } - } - - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private readonly CancellationTokenSource _ansiResponseTokenSource = new (); - - /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (_mainLoopDriver is null) - { - return string.Empty; - } - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.Response); - - _waitAnsiResponse.Set (); - }; - - _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); - - _mainLoopDriver._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - _mainLoopDriver._waitForInput.Set (); - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return string.Empty; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._forceRead = false; - - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) - { - if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.Response)) - { - lock (request!.AnsiRequest._responseLock) - { - // Bad request or no response at all - _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.Response; - } - } - - /// - public override void WriteRaw (string ansi) - { - _mainLoopDriver.WriteRaw (ansi); - } - - public override void Suspend () - { - StopReportingMouseMoves (); - - if (!RunningUnitTests) - { - Platform.Suspend (); - - if (Force16Colors) - { - Curses.Window.Standard.redrawwin (); - Curses.refresh (); - } - } - - StartReportingMouseMoves (); - } - - public override void UpdateCursor () - { - EnsureCursorVisibility (); - - if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) - { - if (Force16Colors) - { - Curses.move (Row, Col); - - Curses.raw (); - Curses.noecho (); - Curses.refresh (); - } - else - { - _mainLoopDriver.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); - } - } - } - public override void UpdateScreen () { if (Force16Colors) @@ -507,10 +281,10 @@ internal class CursesDriver : ConsoleDriver } // SIXELS - foreach (var s in Application.Sixel) + foreach (SixelToRender s in Application.Sixel) { SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y); - Console.Write(s.SixelData); + Console.Write (s.SixelData); } SetCursorPosition (0, 0); @@ -528,258 +302,12 @@ internal class CursesDriver : ConsoleDriver } } - private bool SetCursorPosition (int col, int row) - { - // + 1 is needed because non-Windows is based on 1 instead of 0 and - // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); - - return true; - } - - internal override void End () - { - _ansiResponseTokenSource?.Cancel (); - _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse?.Dispose (); - - StopReportingMouseMoves (); - SetCursorVisibility (CursorVisibility.Default); - - if (RunningUnitTests) - { - return; - } - - // throws away any typeahead that has been typed by - // the user and has not yet been read by the program. - Curses.flushinp (); - - Curses.endwin (); - } - - internal override MainLoop Init () - { - _mainLoopDriver = new UnixMainLoop (this); - - if (!RunningUnitTests) - { - _window = Curses.initscr (); - Curses.set_escdelay (10); - - // Ensures that all procedures are performed at some previous closing. - Curses.doupdate (); - - // - // We are setting Invisible as default, so we could ignore XTerm DECSUSR setting - // - switch (Curses.curs_set (0)) - { - case 0: - _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible; - - break; - - case 1: - _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline; - Curses.curs_set (1); - - break; - - case 2: - _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box; - Curses.curs_set (2); - - break; - - default: - _currentCursorVisibility = _initialCursorVisibility = null; - - break; - } - - if (!Curses.HasColors) - { - throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does."); - } - - Curses.raw (); - Curses.noecho (); - - Curses.Window.Standard.keypad (true); - - Curses.StartColor (); - Curses.UseDefaultColors (); - - if (!RunningUnitTests) - { - Curses.timeout (0); - } - } - - CurrentAttribute = new Attribute (ColorName16.White, ColorName16.Black); - - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - Clipboard = new FakeDriver.FakeClipboard (); - } - else - { - if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) - { - Clipboard = new MacOSXClipboard (); - } - else - { - if (Is_WSL_Platform ()) - { - Clipboard = new WSLClipboard (); - } - else - { - Clipboard = new CursesClipboard (); - } - } - } - - ClearContents (); - StartReportingMouseMoves (); - - if (!RunningUnitTests) - { - Curses.CheckWinChange (); - ClearContents (); - - if (Force16Colors) - { - Curses.refresh (); - } - } - - return new MainLoop (_mainLoopDriver); - } - - internal void ProcessInput (UnixMainLoop.PollData inputEvent) - { - switch (inputEvent.EventType) - { - case UnixMainLoop.EventType.Key: - ConsoleKeyInfo consoleKeyInfo = inputEvent.KeyEvent; - - KeyCode map = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); - - if (map == KeyCode.Null) - { - break; - } - - OnKeyDown (new Key (map)); - OnKeyUp (new Key (map)); - - break; - case UnixMainLoop.EventType.Mouse: - MouseEventArgs me = new MouseEventArgs { Position = inputEvent.MouseEvent.Position, Flags = inputEvent.MouseEvent.MouseFlags }; - OnMouseEvent (me); - - break; - case UnixMainLoop.EventType.WindowSize: - Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height); - ProcessWinChange (inputEvent.WindowSizeEvent.Size); - - break; - default: - throw new ArgumentOutOfRangeException (); - } - } - - private void ProcessWinChange (Size size) - { - if (!RunningUnitTests && Curses.ChangeWindowSize (size.Height, size.Width)) - { - ClearContents (); - OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); - } - } - - private static KeyCode MapCursesKey (int cursesKey) - { - switch (cursesKey) - { - case Curses.KeyF1: return KeyCode.F1; - case Curses.KeyF2: return KeyCode.F2; - case Curses.KeyF3: return KeyCode.F3; - case Curses.KeyF4: return KeyCode.F4; - case Curses.KeyF5: return KeyCode.F5; - case Curses.KeyF6: return KeyCode.F6; - case Curses.KeyF7: return KeyCode.F7; - case Curses.KeyF8: return KeyCode.F8; - case Curses.KeyF9: return KeyCode.F9; - case Curses.KeyF10: return KeyCode.F10; - case Curses.KeyF11: return KeyCode.F11; - case Curses.KeyF12: return KeyCode.F12; - case Curses.KeyUp: return KeyCode.CursorUp; - case Curses.KeyDown: return KeyCode.CursorDown; - case Curses.KeyLeft: return KeyCode.CursorLeft; - case Curses.KeyRight: return KeyCode.CursorRight; - case Curses.KeyHome: return KeyCode.Home; - case Curses.KeyEnd: return KeyCode.End; - case Curses.KeyNPage: return KeyCode.PageDown; - case Curses.KeyPPage: return KeyCode.PageUp; - case Curses.KeyDeleteChar: return KeyCode.Delete; - case Curses.KeyInsertChar: return KeyCode.Insert; - case Curses.KeyTab: return KeyCode.Tab; - case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask; - case Curses.KeyBackspace: return KeyCode.Backspace; - case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask; - case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask; - case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask; - case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask; - case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask; - case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask; - case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask; - case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask; - case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask; - case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask; - case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask; - case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask; - case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask; - case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask; - case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask; - case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask; - case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask; - case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask; - case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask; - case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask; - case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask; - case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask; - case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask; - case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask; - case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask; - case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask; - case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask; - default: return KeyCode.Null; - } - } + #endregion Screen and Contents #region Color Handling + public override bool SupportsTrueColor => true; + /// Creates an Attribute from the provided curses-based foreground and background color numbers /// Contains the curses color number for the foreground (color, plus any attributes) /// Contains the curses color number for the background (color, plus any attributes) @@ -792,11 +320,11 @@ internal class CursesDriver : ConsoleDriver // TODO: for TrueColor - Use InitExtendedPair Curses.InitColorPair (v, foreground, background); - return new Attribute ( - Curses.ColorPair (v), - CursesColorNumberToColorName16 (foreground), - CursesColorNumberToColorName16 (background) - ); + return new ( + Curses.ColorPair (v), + CursesColorNumberToColorName16 (foreground), + CursesColorNumberToColorName16 (background) + ); } /// @@ -815,11 +343,11 @@ internal class CursesDriver : ConsoleDriver ); } - return new Attribute ( - 0, - foreground, - background - ); + return new ( + 0, + foreground, + background + ); } private static short ColorNameToCursesColorNumber (ColorName16 color) @@ -905,8 +433,501 @@ internal class CursesDriver : ConsoleDriver } #endregion + + #region Cursor Support + + private CursorVisibility? _currentCursorVisibility; + private CursorVisibility? _initialCursorVisibility; + + /// + public override bool EnsureCursorVisibility () + { + if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) + { + GetCursorVisibility (out CursorVisibility cursorVisibility); + _currentCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + return false; + } + + SetCursorVisibility (_currentCursorVisibility ?? CursorVisibility.Default); + + return _currentCursorVisibility == CursorVisibility.Default; + } + + /// + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + visibility = CursorVisibility.Invisible; + + if (!_currentCursorVisibility.HasValue) + { + return false; + } + + visibility = _currentCursorVisibility.Value; + + return true; + } + + /// + public override bool SetCursorVisibility (CursorVisibility visibility) + { + if (_initialCursorVisibility.HasValue == false) + { + return false; + } + + if (!RunningUnitTests) + { + Curses.curs_set (((int)visibility >> 16) & 0x000000FF); + } + + if (visibility != CursorVisibility.Invisible) + { + Console.Out.Write ( + EscSeqUtils.CSI_SetCursorStyle ( + (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) + & 0xFF) + ) + ); + } + + _currentCursorVisibility = visibility; + + return true; + } + + public override void UpdateCursor () + { + EnsureCursorVisibility (); + + if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) + { + if (Force16Colors) + { + Curses.move (Row, Col); + + Curses.raw (); + Curses.noecho (); + Curses.refresh (); + } + else + { + _mainLoopDriver.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + } + } + } + + #endregion Cursor Support + + #region Keyboard Support + + public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) + { + KeyCode key; + + if (consoleKey == ConsoleKey.Packet) + { + var mod = new ConsoleModifiers (); + + if (shift) + { + mod |= ConsoleModifiers.Shift; + } + + if (alt) + { + mod |= ConsoleModifiers.Alt; + } + + if (control) + { + mod |= ConsoleModifiers.Control; + } + + var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); + cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); + key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo); + } + else + { + key = (KeyCode)keyChar; + } + + OnKeyDown (new (key)); + OnKeyUp (new (key)); + + //OnKeyPressed (new KeyEventArgsEventArgs (key)); + } + + // TODO: Unused- Remove + private static KeyCode MapCursesKey (int cursesKey) + { + switch (cursesKey) + { + case Curses.KeyF1: return KeyCode.F1; + case Curses.KeyF2: return KeyCode.F2; + case Curses.KeyF3: return KeyCode.F3; + case Curses.KeyF4: return KeyCode.F4; + case Curses.KeyF5: return KeyCode.F5; + case Curses.KeyF6: return KeyCode.F6; + case Curses.KeyF7: return KeyCode.F7; + case Curses.KeyF8: return KeyCode.F8; + case Curses.KeyF9: return KeyCode.F9; + case Curses.KeyF10: return KeyCode.F10; + case Curses.KeyF11: return KeyCode.F11; + case Curses.KeyF12: return KeyCode.F12; + case Curses.KeyUp: return KeyCode.CursorUp; + case Curses.KeyDown: return KeyCode.CursorDown; + case Curses.KeyLeft: return KeyCode.CursorLeft; + case Curses.KeyRight: return KeyCode.CursorRight; + case Curses.KeyHome: return KeyCode.Home; + case Curses.KeyEnd: return KeyCode.End; + case Curses.KeyNPage: return KeyCode.PageDown; + case Curses.KeyPPage: return KeyCode.PageUp; + case Curses.KeyDeleteChar: return KeyCode.Delete; + case Curses.KeyInsertChar: return KeyCode.Insert; + case Curses.KeyTab: return KeyCode.Tab; + case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask; + case Curses.KeyBackspace: return KeyCode.Backspace; + case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask; + case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask; + case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask; + case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask; + case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask; + case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask; + case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask; + case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask; + case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask; + case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask; + case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask; + case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask; + case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask; + case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask; + case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask; + case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask; + case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask; + case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask; + case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask; + case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask; + case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask; + case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask; + case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask; + case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask; + case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask; + case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask; + case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask; + default: return KeyCode.Null; + } + } + + #endregion Keyboard Support + + #region Mouse Support + public void StartReportingMouseMoves () + { + if (!RunningUnitTests) + { + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + } + } + + public void StopReportingMouseMoves () + { + if (!RunningUnitTests) + { + Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + } + } + + #endregion Mouse Support + + private bool SetCursorPosition (int col, int row) + { + // + 1 is needed because non-Windows is based on 1 instead of 0 and + // Console.CursorTop/CursorLeft isn't reliable. + Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); + + return true; + } + + #region Init/End/MainLoop + + public Curses.Window _window; + private UnixMainLoop _mainLoopDriver; + + internal override MainLoop Init () + { + _mainLoopDriver = new (this); + + if (!RunningUnitTests) + { + _window = Curses.initscr (); + Curses.set_escdelay (10); + + // Ensures that all procedures are performed at some previous closing. + Curses.doupdate (); + + // + // We are setting Invisible as default, so we could ignore XTerm DECSUSR setting + // + switch (Curses.curs_set (0)) + { + case 0: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible; + + break; + + case 1: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline; + Curses.curs_set (1); + + break; + + case 2: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box; + Curses.curs_set (2); + + break; + + default: + _currentCursorVisibility = _initialCursorVisibility = null; + + break; + } + + if (!Curses.HasColors) + { + throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does."); + } + + Curses.raw (); + Curses.noecho (); + + Curses.Window.Standard.keypad (true); + + Curses.StartColor (); + Curses.UseDefaultColors (); + + if (!RunningUnitTests) + { + Curses.timeout (0); + } + } + + CurrentAttribute = new (ColorName16.White, ColorName16.Black); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + Clipboard = new FakeDriver.FakeClipboard (); + } + else + { + if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) + { + Clipboard = new MacOSXClipboard (); + } + else + { + if (Is_WSL_Platform ()) + { + Clipboard = new WSLClipboard (); + } + else + { + Clipboard = new CursesClipboard (); + } + } + } + + ClearContents (); + StartReportingMouseMoves (); + + if (!RunningUnitTests) + { + Curses.CheckWinChange (); + ClearContents (); + + if (Force16Colors) + { + Curses.refresh (); + } + } + + return new (_mainLoopDriver); + } + + internal void ProcessInput (UnixMainLoop.PollData inputEvent) + { + switch (inputEvent.EventType) + { + case UnixMainLoop.EventType.Key: + ConsoleKeyInfo consoleKeyInfo = inputEvent.KeyEvent; + + KeyCode map = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); + + if (map == KeyCode.Null) + { + break; + } + + OnKeyDown (new (map)); + OnKeyUp (new (map)); + + break; + case UnixMainLoop.EventType.Mouse: + var me = new MouseEventArgs { Position = inputEvent.MouseEvent.Position, Flags = inputEvent.MouseEvent.MouseFlags }; + OnMouseEvent (me); + + break; + case UnixMainLoop.EventType.WindowSize: + Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height); + ProcessWinChange (inputEvent.WindowSizeEvent.Size); + + break; + default: + throw new ArgumentOutOfRangeException (); + } + } + private void ProcessWinChange (Size size) + { + if (!RunningUnitTests && Curses.ChangeWindowSize (size.Height, size.Width)) + { + ClearContents (); + OnSizeChanged (new (new (Cols, Rows))); + } + } + + internal override void End () + { + _ansiResponseTokenSource?.Cancel (); + _ansiResponseTokenSource?.Dispose (); + _waitAnsiResponse?.Dispose (); + + StopReportingMouseMoves (); + SetCursorVisibility (CursorVisibility.Default); + + if (RunningUnitTests) + { + return; + } + + // throws away any typeahead that has been typed by + // the user and has not yet been read by the program. + Curses.flushinp (); + + Curses.endwin (); + } + + #endregion Init/End/MainLoop + + public static bool Is_WSL_Platform () + { + // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell + //if (new CursesClipboard ().IsSupported) { + // // If xclip is installed on Linux under WSL, this will return true. + // return false; + //} + (int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true); + + if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) + { + return true; + } + + return false; + } + #region Low-Level Unix Stuff + + + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + + /// + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + { + if (_mainLoopDriver is null) + { + return string.Empty; + } + + try + { + lock (ansiRequest._responseLock) + { + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == ansiRequest); + Debug.Assert (e == ansiRequest.Response); + + _waitAnsiResponse.Set (); + }; + + _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); + + _mainLoopDriver._forceRead = true; + } + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + _mainLoopDriver._waitForInput.Set (); + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return string.Empty; + } + + lock (ansiRequest._responseLock) + { + _mainLoopDriver._forceRead = false; + + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + { + if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.Response)) + { + lock (request!.AnsiRequest._responseLock) + { + // Bad request or no response at all + _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + } + } + } + + _waitAnsiResponse.Reset (); + + return ansiRequest.Response; + } + } + + /// + public override void WriteRaw (string ansi) { _mainLoopDriver.WriteRaw (ansi); } + } +// TODO: One type per file - move to another file internal static class Platform { private static int _suspendSignal; @@ -986,3 +1007,5 @@ internal static class Platform [DllImport ("libc")] private static extern int uname (nint buf); } + +#endregion Low-Level Unix Stuff diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c index d289b7ef3..6ca9471d2 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c @@ -1,11 +1,12 @@ #include #include -// This function is used to get the value of the TIOCGWINSZ variable, +// Used to get the value of the TIOCGWINSZ variable, // which may have different values ​​on different Unix operating systems. -// In Linux=0x005413, in Darwin and OpenBSD=0x40087468, -// In Solaris=0x005468 -// The best solution is having a function that get the real value of the current OS +// Linux=0x005413 +// Darwin and OpenBSD=0x40087468, +// Solaris=0x005468 +// See https://stackoverflow.com/questions/16237137/what-is-termios-tiocgwinsz int get_tiocgwinsz_value() { return TIOCGWINSZ; } \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index c96c8f08b..280f3f9e7 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -4,20 +4,7 @@ using System.Collections.Concurrent; namespace Terminal.Gui; -/// -/// Represents the status of an ANSI escape sequence request made to the terminal using -/// . -/// -/// -public class EscSeqReqStatus -{ - /// Creates a new state of escape sequence request. - /// The object. - public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; } - - /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). - public AnsiEscapeSequenceRequest AnsiRequest { get; } -} +// QUESTION: Can this class be moved/refactored/combined with the new AnsiEscapeSequenceRequest/Response class? // TODO: This class is a singleton. It should use the singleton pattern. /// diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs new file mode 100644 index 000000000..c9561ec7b --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs @@ -0,0 +1,17 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// Represents the status of an ANSI escape sequence request made to the terminal using +/// . +/// +/// +public class EscSeqReqStatus +{ + /// Creates a new state of escape sequence request. + /// The object. + public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; } + + /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). + public AnsiEscapeSequenceRequest AnsiRequest { get; } +} diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 4b283afb1..99e5bce8f 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -3,6 +3,13 @@ using Terminal.Gui.ConsoleDrivers; namespace Terminal.Gui; +// QUESTION: Should this class be refactored into separate classes for: +// QUESTION: CSI definitions +// QUESTION: Primitives like DecodeEsqReq +// QUESTION: Screen/Color/Cursor handling +// QUESTION: Mouse handling +// QUESTION: Keyboard handling + /// /// Provides a platform-independent API for managing ANSI escape sequences. /// @@ -14,6 +21,7 @@ namespace Terminal.Gui; /// public static class EscSeqUtils { + // TODO: One type per file - Move this enum to a separate file. /// /// Options for ANSI ESC "[xJ" - Clears part of the screen. /// @@ -40,6 +48,9 @@ public static class EscSeqUtils EntireScreenAndScrollbackBuffer = 3 } + // QUESTION: I wonder if EscSeqUtils.CSI_... should be more strongly typed such that this (and Terminator could be + // QUESTION: public required CSIRequests Request { get; init; } + // QUESTION: public required CSITerminators Terminator { get; init; } /// /// Escape key code (ASCII 27/0x1B). /// @@ -419,6 +430,7 @@ public static class EscSeqUtils }; } + /// /// Gets the depending on terminating and value. /// @@ -1721,7 +1733,7 @@ public static class EscSeqUtils /// https://terminalguide.namepad.de/seq/csi_st-18/ /// The terminator indicating a reply to : ESC [ 8 ; height ; width t /// - public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" }; + public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", ExpectedResponseValue = "8" }; #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/KeyCode.cs b/Terminal.Gui/ConsoleDrivers/KeyCode.cs new file mode 100644 index 000000000..183322ec8 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/KeyCode.cs @@ -0,0 +1,321 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// The enumeration encodes key information from s and provides a +/// consistent way for application code to specify keys and receive key events. +/// +/// The class provides a higher-level abstraction, with helper methods and properties for +/// common operations. For example, and provide a convenient way +/// to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed. +/// +/// +/// +/// +/// Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a +/// keyboard. Enum values are provided for these (e.g. , , etc.). +/// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent +/// *lowercase*, un-shifted characters. +/// +/// +/// Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. , +/// , etc.). +/// +/// +/// The shift modifiers (, , and +/// ) can be combined (with logical or) with the other key codes to represent shifted +/// keys. For example, the enum value represents the un-shifted 'a' key, while +/// | represents the 'A' key (shifted 'a' key). Likewise, +/// | represents the 'Alt+A' key combination. +/// +/// +/// All other keys that produce a printable character are encoded as the Unicode value of the character. For +/// example, the for the '!' character is 33, which is the Unicode value for '!'. Likewise, +/// `â` is 226, `Â` is 194, etc. +/// +/// +/// If the is set, then the value is that of the special mask, otherwise, the value is +/// the one of the lower bits (as extracted by ). +/// +/// +[Flags] +public enum KeyCode : uint +{ + /// + /// Mask that indicates that the key is a unicode codepoint. Values outside this range indicate the key has shift + /// modifiers or is a special key like function keys, arrows keys and so on. + /// + CharMask = 0x_f_ffff, + + /// + /// If the is set, then the value is that of the special mask, otherwise, the value is + /// in the lower bits (as extracted by ). + /// + SpecialMask = 0x_fff0_0000, + + /// + /// When this value is set, the Key encodes the sequence Shift-KeyValue. The actual value must be extracted by + /// removing the ShiftMask. + /// + ShiftMask = 0x_1000_0000, + + /// + /// When this value is set, the Key encodes the sequence Alt-KeyValue. The actual value must be extracted by + /// removing the AltMask. + /// + AltMask = 0x_8000_0000, + + /// + /// When this value is set, the Key encodes the sequence Ctrl-KeyValue. The actual value must be extracted by + /// removing the CtrlMask. + /// + CtrlMask = 0x_4000_0000, + + /// The key code representing an invalid or empty key. + Null = 0, + + /// Backspace key. + Backspace = 8, + + /// The key code for the tab key (forwards tab key). + Tab = 9, + + /// The key code for the return key. + Enter = ConsoleKey.Enter, + + /// The key code for the clear key. + Clear = 12, + + /// The key code for the escape key. + Esc = 27, + + /// The key code for the space bar key. + Space = 32, + + /// Digit 0. + D0 = 48, + + /// Digit 1. + D1, + + /// Digit 2. + D2, + + /// Digit 3. + D3, + + /// Digit 4. + D4, + + /// Digit 5. + D5, + + /// Digit 6. + D6, + + /// Digit 7. + D7, + + /// Digit 8. + D8, + + /// Digit 9. + D9, + + /// The key code for the A key + A = 65, + + /// The key code for the B key + B, + + /// The key code for the C key + C, + + /// The key code for the D key + D, + + /// The key code for the E key + E, + + /// The key code for the F key + F, + + /// The key code for the G key + G, + + /// The key code for the H key + H, + + /// The key code for the I key + I, + + /// The key code for the J key + J, + + /// The key code for the K key + K, + + /// The key code for the L key + L, + + /// The key code for the M key + M, + + /// The key code for the N key + N, + + /// The key code for the O key + O, + + /// The key code for the P key + P, + + /// The key code for the Q key + Q, + + /// The key code for the R key + R, + + /// The key code for the S key + S, + + /// The key code for the T key + T, + + /// The key code for the U key + U, + + /// The key code for the V key + V, + + /// The key code for the W key + W, + + /// The key code for the X key + X, + + /// The key code for the Y key + Y, + + /// The key code for the Z key + Z, + + ///// + ///// The key code for the Delete key. + ///// + //Delete = 127, + + // --- Special keys --- + // The values below are common non-alphanum keys. Their values are + // based on the .NET ConsoleKey values, which, in-turn are based on the + // VK_ values from the Windows API. + // We add MaxCodePoint to avoid conflicts with the Unicode values. + + /// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys. + MaxCodePoint = 0x10FFFF, + + /// Cursor up key + CursorUp = MaxCodePoint + ConsoleKey.UpArrow, + + /// Cursor down key. + CursorDown = MaxCodePoint + ConsoleKey.DownArrow, + + /// Cursor left key. + CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow, + + /// Cursor right key. + CursorRight = MaxCodePoint + ConsoleKey.RightArrow, + + /// Page Up key. + PageUp = MaxCodePoint + ConsoleKey.PageUp, + + /// Page Down key. + PageDown = MaxCodePoint + ConsoleKey.PageDown, + + /// Home key. + Home = MaxCodePoint + ConsoleKey.Home, + + /// End key. + End = MaxCodePoint + ConsoleKey.End, + + /// Insert (INS) key. + Insert = MaxCodePoint + ConsoleKey.Insert, + + /// Delete (DEL) key. + Delete = MaxCodePoint + ConsoleKey.Delete, + + /// Print screen character key. + PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen, + + /// F1 key. + F1 = MaxCodePoint + ConsoleKey.F1, + + /// F2 key. + F2 = MaxCodePoint + ConsoleKey.F2, + + /// F3 key. + F3 = MaxCodePoint + ConsoleKey.F3, + + /// F4 key. + F4 = MaxCodePoint + ConsoleKey.F4, + + /// F5 key. + F5 = MaxCodePoint + ConsoleKey.F5, + + /// F6 key. + F6 = MaxCodePoint + ConsoleKey.F6, + + /// F7 key. + F7 = MaxCodePoint + ConsoleKey.F7, + + /// F8 key. + F8 = MaxCodePoint + ConsoleKey.F8, + + /// F9 key. + F9 = MaxCodePoint + ConsoleKey.F9, + + /// F10 key. + F10 = MaxCodePoint + ConsoleKey.F10, + + /// F11 key. + F11 = MaxCodePoint + ConsoleKey.F11, + + /// F12 key. + F12 = MaxCodePoint + ConsoleKey.F12, + + /// F13 key. + F13 = MaxCodePoint + ConsoleKey.F13, + + /// F14 key. + F14 = MaxCodePoint + ConsoleKey.F14, + + /// F15 key. + F15 = MaxCodePoint + ConsoleKey.F15, + + /// F16 key. + F16 = MaxCodePoint + ConsoleKey.F16, + + /// F17 key. + F17 = MaxCodePoint + ConsoleKey.F17, + + /// F18 key. + F18 = MaxCodePoint + ConsoleKey.F18, + + /// F19 key. + F19 = MaxCodePoint + ConsoleKey.F19, + + /// F20 key. + F20 = MaxCodePoint + ConsoleKey.F20, + + /// F21 key. + F21 = MaxCodePoint + ConsoleKey.F21, + + /// F22 key. + F22 = MaxCodePoint + ConsoleKey.F22, + + /// F23 key. + F23 = MaxCodePoint + ConsoleKey.F23, + + /// F24 key. + F24 = MaxCodePoint + ConsoleKey.F24 +} diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs deleted file mode 100644 index afa78ff6a..000000000 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ /dev/null @@ -1,2007 +0,0 @@ -// -// NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. -// - -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; -using static Terminal.Gui.NetEvents; - -namespace Terminal.Gui; - -internal class NetWinVTConsole -{ - private const uint DISABLE_NEWLINE_AUTO_RETURN = 8; - private const uint ENABLE_ECHO_INPUT = 4; - private const uint ENABLE_EXTENDED_FLAGS = 128; - private const uint ENABLE_INSERT_MODE = 32; - private const uint ENABLE_LINE_INPUT = 2; - private const uint ENABLE_LVB_GRID_WORLDWIDE = 10; - private const uint ENABLE_MOUSE_INPUT = 16; - - // Input modes. - private const uint ENABLE_PROCESSED_INPUT = 1; - - // Output modes. - private const uint ENABLE_PROCESSED_OUTPUT = 1; - private const uint ENABLE_QUICK_EDIT_MODE = 64; - private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; - private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; - private const uint ENABLE_WINDOW_INPUT = 8; - private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2; - private const int STD_ERROR_HANDLE = -12; - private const int STD_INPUT_HANDLE = -10; - private const int STD_OUTPUT_HANDLE = -11; - - private readonly nint _errorHandle; - private readonly nint _inputHandle; - private readonly uint _originalErrorConsoleMode; - private readonly uint _originalInputConsoleMode; - private readonly uint _originalOutputConsoleMode; - private readonly nint _outputHandle; - - public NetWinVTConsole () - { - _inputHandle = GetStdHandle (STD_INPUT_HANDLE); - - if (!GetConsoleMode (_inputHandle, out uint mode)) - { - throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}."); - } - - _originalInputConsoleMode = mode; - - if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) - { - mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; - - if (!SetConsoleMode (_inputHandle, mode)) - { - throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}."); - } - } - - _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); - - if (!GetConsoleMode (_outputHandle, out mode)) - { - throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}."); - } - - _originalOutputConsoleMode = mode; - - if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) - { - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; - - if (!SetConsoleMode (_outputHandle, mode)) - { - throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}."); - } - } - - _errorHandle = GetStdHandle (STD_ERROR_HANDLE); - - if (!GetConsoleMode (_errorHandle, out mode)) - { - throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}."); - } - - _originalErrorConsoleMode = mode; - - if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN) - { - mode |= DISABLE_NEWLINE_AUTO_RETURN; - - if (!SetConsoleMode (_errorHandle, mode)) - { - throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}."); - } - } - } - - public void Cleanup () - { - if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode)) - { - throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}."); - } - - if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode)) - { - throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}."); - } - - if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode)) - { - throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}."); - } - } - - [DllImport ("kernel32.dll")] - private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); - - [DllImport ("kernel32.dll")] - private static extern uint GetLastError (); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern nint GetStdHandle (int nStdHandle); - - [DllImport ("kernel32.dll")] - private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode); -} - -internal class NetEvents : IDisposable -{ - private readonly ManualResetEventSlim _inputReady = new (false); - private CancellationTokenSource _inputReadyCancellationTokenSource; - internal readonly ManualResetEventSlim _waitForStart = new (false); - - //CancellationTokenSource _waitForStartCancellationTokenSource; - private readonly ManualResetEventSlim _winChange = new (false); - private readonly ConcurrentQueue _inputQueue = new (); - private readonly ConsoleDriver _consoleDriver; - private ConsoleKeyInfo [] _cki; - private bool _isEscSeq; -#if PROCESS_REQUEST - bool _neededProcessRequest; -#endif - public EscSeqRequests EscSeqRequests { get; } = new (); - - public NetEvents (ConsoleDriver consoleDriver) - { - _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); - _inputReadyCancellationTokenSource = new CancellationTokenSource (); - - Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); - - Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token); - } - - public InputResult? DequeueInput () - { - while (_inputReadyCancellationTokenSource != null - && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested) - { - _waitForStart.Set (); - _winChange.Set (); - - try - { - if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) - { - if (_inputQueue.Count == 0) - { - _inputReady.Wait (_inputReadyCancellationTokenSource.Token); - } - } - } - catch (OperationCanceledException) - { - return null; - } - finally - { - _inputReady.Reset (); - } - -#if PROCESS_REQUEST - _neededProcessRequest = false; -#endif - if (_inputQueue.Count > 0) - { - if (_inputQueue.TryDequeue (out InputResult? result)) - { - return result; - } - } - } - - return null; - } - - private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true) - { - while (!cancellationToken.IsCancellationRequested) - { - // if there is a key available, return it without waiting - // (or dispatching work to the thread queue) - if (Console.KeyAvailable) - { - return Console.ReadKey (intercept); - } - - if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) - { - if (_retries > 1) - { - if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) - { - lock (seqReqStatus!.AnsiRequest._responseLock) - { - EscSeqRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.Response = string.Empty; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } - - if (!_forceRead) - { - Task.Delay (100, cancellationToken).Wait (cancellationToken); - } - } - - cancellationToken.ThrowIfCancellationRequested (); - - return default (ConsoleKeyInfo); - } - - internal bool _forceRead; - private int _retries; - - private void ProcessInputQueue () - { - while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) - { - try - { - if (!_forceRead) - { - _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return; - } - - _waitForStart.Reset (); - - if (_inputQueue.Count == 0 || _forceRead) - { - ConsoleKey key = 0; - ConsoleModifiers mod = 0; - ConsoleKeyInfo newConsoleKeyInfo = default; - - while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) - { - ConsoleKeyInfo consoleKeyInfo; - - try - { - consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - if (EscSeqUtils.IncompleteCkInfos is { }) - { - EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki); - } - - if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) - || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) - { - if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) - { - _cki = EscSeqUtils.ResizeArray ( - new ConsoleKeyInfo ( - (char)KeyCode.Esc, - 0, - false, - false, - false - ), - _cki - ); - } - - _isEscSeq = true; - - if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) - || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) - || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))) - { - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - } - else - { - newConsoleKeyInfo = consoleKeyInfo; - _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); - - if (Console.KeyAvailable) - { - continue; - } - - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - } - - break; - } - - if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) - { - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - - if (Console.KeyAvailable) - { - _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); - } - else - { - ProcessMapConsoleKeyInfo (consoleKeyInfo); - } - - break; - } - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - - if (_retries > 0) - { - _retries = 0; - } - - break; - } - } - - _inputReady.Set (); - } - - void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) - { - _inputQueue.Enqueue ( - new InputResult - { - EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo) - } - ); - _isEscSeq = false; - } - } - - private void CheckWindowSizeChange () - { - void RequestWindowSize (CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - // Wait for a while then check if screen has changed sizes - Task.Delay (500, cancellationToken).Wait (cancellationToken); - - int buffHeight, buffWidth; - - if (((NetDriver)_consoleDriver).IsWinPlatform) - { - buffHeight = Math.Max (Console.BufferHeight, 0); - buffWidth = Math.Max (Console.BufferWidth, 0); - } - else - { - buffHeight = _consoleDriver.Rows; - buffWidth = _consoleDriver.Cols; - } - - if (EnqueueWindowSizeEvent ( - Math.Max (Console.WindowHeight, 0), - Math.Max (Console.WindowWidth, 0), - buffHeight, - buffWidth - )) - { - return; - } - } - - cancellationToken.ThrowIfCancellationRequested (); - } - - while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) - { - try - { - _winChange.Wait (_inputReadyCancellationTokenSource.Token); - _winChange.Reset (); - - RequestWindowSize (_inputReadyCancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - _inputReady.Set (); - } - } - - /// Enqueue a window size event if the window size has changed. - /// - /// - /// - /// - /// - private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth) - { - if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) - { - return false; - } - - int w = Math.Max (winWidth, 0); - int h = Math.Max (winHeight, 0); - - _inputQueue.Enqueue ( - new InputResult - { - EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) } - } - ); - - return true; - } - - // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event) - private void ProcessRequestResponse ( - ref ConsoleKeyInfo newConsoleKeyInfo, - ref ConsoleKey key, - ConsoleKeyInfo [] cki, - ref ConsoleModifiers mod - ) - { - // isMouse is true if it's CSI<, false otherwise - EscSeqUtils.DecodeEscSeq ( - EscSeqRequests, - ref newConsoleKeyInfo, - ref key, - cki, - ref mod, - out string c1Control, - out string code, - out string [] values, - out string terminating, - out bool isMouse, - out List mouseFlags, - out Point pos, - out EscSeqReqStatus seqReqStatus, - (f, p) => HandleMouseEvent (MapMouseFlags (f), p) - ); - - if (isMouse) - { - foreach (MouseFlags mf in mouseFlags) - { - HandleMouseEvent (MapMouseFlags (mf), pos); - } - - return; - } - - if (seqReqStatus is { }) - { - //HandleRequestResponseEvent (c1Control, code, values, terminating); - - var ckiString = EscSeqUtils.ToString (cki); - - lock (seqReqStatus.AnsiRequest._responseLock) - { - seqReqStatus.AnsiRequest.Response = ckiString; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString); - } - - return; - } - - if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator)) - { - if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result)) - { - lock (result.AnsiRequest._responseLock) - { - result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator; - result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator); - - EscSeqUtils.InvalidRequestTerminator = null; - } - } - - return; - } - - if (newConsoleKeyInfo != default) - { - HandleKeyboardEvent (newConsoleKeyInfo); - } - } - - [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] - private MouseButtonState MapMouseFlags (MouseFlags mouseFlags) - { - MouseButtonState mbs = default; - - foreach (object flag in Enum.GetValues (mouseFlags.GetType ())) - { - if (mouseFlags.HasFlag ((MouseFlags)flag)) - { - switch (flag) - { - case MouseFlags.Button1Pressed: - mbs |= MouseButtonState.Button1Pressed; - - break; - case MouseFlags.Button1Released: - mbs |= MouseButtonState.Button1Released; - - break; - case MouseFlags.Button1Clicked: - mbs |= MouseButtonState.Button1Clicked; - - break; - case MouseFlags.Button1DoubleClicked: - mbs |= MouseButtonState.Button1DoubleClicked; - - break; - case MouseFlags.Button1TripleClicked: - mbs |= MouseButtonState.Button1TripleClicked; - - break; - case MouseFlags.Button2Pressed: - mbs |= MouseButtonState.Button2Pressed; - - break; - case MouseFlags.Button2Released: - mbs |= MouseButtonState.Button2Released; - - break; - case MouseFlags.Button2Clicked: - mbs |= MouseButtonState.Button2Clicked; - - break; - case MouseFlags.Button2DoubleClicked: - mbs |= MouseButtonState.Button2DoubleClicked; - - break; - case MouseFlags.Button2TripleClicked: - mbs |= MouseButtonState.Button2TripleClicked; - - break; - case MouseFlags.Button3Pressed: - mbs |= MouseButtonState.Button3Pressed; - - break; - case MouseFlags.Button3Released: - mbs |= MouseButtonState.Button3Released; - - break; - case MouseFlags.Button3Clicked: - mbs |= MouseButtonState.Button3Clicked; - - break; - case MouseFlags.Button3DoubleClicked: - mbs |= MouseButtonState.Button3DoubleClicked; - - break; - case MouseFlags.Button3TripleClicked: - mbs |= MouseButtonState.Button3TripleClicked; - - break; - case MouseFlags.WheeledUp: - mbs |= MouseButtonState.ButtonWheeledUp; - - break; - case MouseFlags.WheeledDown: - mbs |= MouseButtonState.ButtonWheeledDown; - - break; - case MouseFlags.WheeledLeft: - mbs |= MouseButtonState.ButtonWheeledLeft; - - break; - case MouseFlags.WheeledRight: - mbs |= MouseButtonState.ButtonWheeledRight; - - break; - case MouseFlags.Button4Pressed: - mbs |= MouseButtonState.Button4Pressed; - - break; - case MouseFlags.Button4Released: - mbs |= MouseButtonState.Button4Released; - - break; - case MouseFlags.Button4Clicked: - mbs |= MouseButtonState.Button4Clicked; - - break; - case MouseFlags.Button4DoubleClicked: - mbs |= MouseButtonState.Button4DoubleClicked; - - break; - case MouseFlags.Button4TripleClicked: - mbs |= MouseButtonState.Button4TripleClicked; - - break; - case MouseFlags.ButtonShift: - mbs |= MouseButtonState.ButtonShift; - - break; - case MouseFlags.ButtonCtrl: - mbs |= MouseButtonState.ButtonCtrl; - - break; - case MouseFlags.ButtonAlt: - mbs |= MouseButtonState.ButtonAlt; - - break; - case MouseFlags.ReportMousePosition: - mbs |= MouseButtonState.ReportMousePosition; - - break; - case MouseFlags.AllEvents: - mbs |= MouseButtonState.AllEvents; - - break; - } - } - } - - return mbs; - } - - private Point _lastCursorPosition; - - //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) - //{ - // if (terminating == - - // // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. - // // The observation is correct because the response isn't immediate and this is useless - // EscSeqUtils.CSI_RequestCursorPositionReport.Terminator) - // { - // var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; - - // if (_lastCursorPosition.Y != point.Y) - // { - // _lastCursorPosition = point; - // var eventType = EventType.WindowPosition; - // var winPositionEv = new WindowPositionEvent { CursorPosition = point }; - - // _inputQueue.Enqueue ( - // new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } - // ); - // } - // else - // { - // return; - // } - // } - // else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator) - // { - // if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value) - // { - // EnqueueWindowSizeEvent ( - // Math.Max (int.Parse (values [1]), 0), - // Math.Max (int.Parse (values [2]), 0), - // Math.Max (int.Parse (values [1]), 0), - // Math.Max (int.Parse (values [2]), 0) - // ); - // } - // else - // { - // EnqueueRequestResponseEvent (c1Control, code, values, terminating); - // } - // } - // else - // { - // EnqueueRequestResponseEvent (c1Control, code, values, terminating); - // } - - // _inputReady.Set (); - //} - - //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) - //{ - // var eventType = EventType.RequestResponse; - // var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) }; - - // _inputQueue.Enqueue ( - // new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv } - // ); - //} - - private void HandleMouseEvent (MouseButtonState buttonState, Point pos) - { - var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState }; - - _inputQueue.Enqueue ( - new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent } - ); - - _inputReady.Set (); - } - - public enum EventType - { - Key = 1, - Mouse = 2, - WindowSize = 3, - WindowPosition = 4, - RequestResponse = 5 - } - - [Flags] - public enum MouseButtonState - { - Button1Pressed = 0x1, - Button1Released = 0x2, - Button1Clicked = 0x4, - Button1DoubleClicked = 0x8, - Button1TripleClicked = 0x10, - Button2Pressed = 0x20, - Button2Released = 0x40, - Button2Clicked = 0x80, - Button2DoubleClicked = 0x100, - Button2TripleClicked = 0x200, - Button3Pressed = 0x400, - Button3Released = 0x800, - Button3Clicked = 0x1000, - Button3DoubleClicked = 0x2000, - Button3TripleClicked = 0x4000, - ButtonWheeledUp = 0x8000, - ButtonWheeledDown = 0x10000, - ButtonWheeledLeft = 0x20000, - ButtonWheeledRight = 0x40000, - Button4Pressed = 0x80000, - Button4Released = 0x100000, - Button4Clicked = 0x200000, - Button4DoubleClicked = 0x400000, - Button4TripleClicked = 0x800000, - ButtonShift = 0x1000000, - ButtonCtrl = 0x2000000, - ButtonAlt = 0x4000000, - ReportMousePosition = 0x8000000, - AllEvents = -1 - } - - public struct MouseEvent - { - public Point Position; - public MouseButtonState ButtonState; - } - - public struct WindowSizeEvent - { - public Size Size; - } - - public struct WindowPositionEvent - { - public int Top; - public int Left; - public Point CursorPosition; - } - - public struct RequestResponseEvent - { - public (string c1Control, string code, string [] values, string terminating) ResultTuple; - } - - public struct InputResult - { - public EventType EventType; - public ConsoleKeyInfo ConsoleKeyInfo; - public MouseEvent MouseEvent; - public WindowSizeEvent WindowSizeEvent; - public WindowPositionEvent WindowPositionEvent; - public RequestResponseEvent RequestResponseEvent; - - public readonly override string ToString () - { - return EventType switch - { - EventType.Key => ToString (ConsoleKeyInfo), - EventType.Mouse => MouseEvent.ToString (), - - //EventType.WindowSize => WindowSize.ToString (), - //EventType.RequestResponse => RequestResponse.ToString (), - _ => "Unknown event type: " + EventType - }; - } - - /// Prints a ConsoleKeyInfoEx structure - /// - /// - public readonly string ToString (ConsoleKeyInfo cki) - { - var ke = new Key ((KeyCode)cki.KeyChar); - var sb = new StringBuilder (); - sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})"); - sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); - sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); - sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); - sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) "); - string s = sb.ToString ().TrimEnd (',').TrimEnd (' '); - - return $"[ConsoleKeyInfo({s})]"; - } - } - - private void HandleKeyboardEvent (ConsoleKeyInfo cki) - { - var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki }; - - _inputQueue.Enqueue (inputResult); - } - - public void Dispose () - { - _inputReadyCancellationTokenSource?.Cancel (); - _inputReadyCancellationTokenSource?.Dispose (); - _inputReadyCancellationTokenSource = null; - - try - { - // throws away any typeahead that has been typed by - // the user and has not yet been read by the program. - while (Console.KeyAvailable) - { - Console.ReadKey (true); - } - } - catch (InvalidOperationException) - { - // Ignore - Console input has already been closed - } - } -} - -internal class NetDriver : ConsoleDriver -{ - private const int COLOR_BLACK = 30; - private const int COLOR_BLUE = 34; - private const int COLOR_BRIGHT_BLACK = 90; - private const int COLOR_BRIGHT_BLUE = 94; - private const int COLOR_BRIGHT_CYAN = 96; - private const int COLOR_BRIGHT_GREEN = 92; - private const int COLOR_BRIGHT_MAGENTA = 95; - private const int COLOR_BRIGHT_RED = 91; - private const int COLOR_BRIGHT_WHITE = 97; - private const int COLOR_BRIGHT_YELLOW = 93; - private const int COLOR_CYAN = 36; - private const int COLOR_GREEN = 32; - private const int COLOR_MAGENTA = 35; - private const int COLOR_RED = 31; - private const int COLOR_WHITE = 37; - private const int COLOR_YELLOW = 33; - internal NetMainLoop _mainLoopDriver; - public bool IsWinPlatform { get; private set; } - public NetWinVTConsole NetWinConsole { get; private set; } - - public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix - || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931); - - public override void Refresh () - { - UpdateScreen (); - UpdateCursor (); - } - - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) - { - var input = new InputResult - { - EventType = EventType.Key, ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control) - }; - - try - { - ProcessInput (input); - } - catch (OverflowException) - { } - } - - public override void Suspend () - { - if (Environment.OSVersion.Platform != PlatformID.Unix) - { - return; - } - - StopReportingMouseMoves (); - - if (!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); - - SetContentsAsDirty (); - Refresh (); - } - - StartReportingMouseMoves (); - } - - public override void UpdateScreen () - { - if (RunningUnitTests - || _winSizeChanging - || Console.WindowHeight < 1 - || Contents.Length != Rows * Cols - || Rows != Console.WindowHeight) - { - return; - } - - var top = 0; - var left = 0; - int rows = Rows; - int cols = Cols; - var output = new StringBuilder (); - Attribute? redrawAttr = null; - int lastCol = -1; - - CursorVisibility? savedVisibility = _cachedCursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - - for (int row = top; row < rows; row++) - { - if (Console.WindowHeight < 1) - { - return; - } - - if (!_dirtyLines [row]) - { - continue; - } - - if (!SetCursorPosition (0, row)) - { - return; - } - - _dirtyLines [row] = false; - output.Clear (); - - for (int col = left; col < cols; col++) - { - lastCol = -1; - var outputWidth = 0; - - for (; col < cols; col++) - { - if (!Contents [row, col].IsDirty) - { - if (output.Length > 0) - { - WriteToConsole (output, ref lastCol, row, ref outputWidth); - } - else if (lastCol == -1) - { - lastCol = col; - } - - if (lastCol + 1 < cols) - { - lastCol++; - } - - continue; - } - - if (lastCol == -1) - { - lastCol = col; - } - - Attribute attr = Contents [row, col].Attribute.Value; - - // Performance: Only send the escape sequence if the attribute has changed. - if (attr != redrawAttr) - { - redrawAttr = attr; - - if (Force16Colors) - { - output.Append ( - EscSeqUtils.CSI_SetGraphicsRendition ( - MapColors ( - (ConsoleColor)attr.Background.GetClosestNamedColor16 (), - false - ), - MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ()) - ) - ); - } - else - { - output.Append ( - EscSeqUtils.CSI_SetForegroundColorRGB ( - attr.Foreground.R, - attr.Foreground.G, - attr.Foreground.B - ) - ); - - output.Append ( - EscSeqUtils.CSI_SetBackgroundColorRGB ( - attr.Background.R, - attr.Background.G, - attr.Background.B - ) - ); - } - } - - outputWidth++; - Rune rune = Contents [row, col].Rune; - output.Append (rune); - - if (Contents [row, col].CombiningMarks.Count > 0) - { - // AtlasEngine does not support NON-NORMALIZED combining marks in a way - // compatible with the driver architecture. Any CMs (except in the first col) - // are correctly combined with the base char, but are ALSO treated as 1 column - // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. - // - // For now, we just ignore the list of CMs. - //foreach (var combMark in Contents [row, col].CombiningMarks) { - // output.Append (combMark); - //} - // WriteToConsole (output, ref lastCol, row, ref outputWidth); - } - else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) - { - WriteToConsole (output, ref lastCol, row, ref outputWidth); - SetCursorPosition (col - 1, row); - } - - Contents [row, col].IsDirty = false; - } - } - - if (output.Length > 0) - { - SetCursorPosition (lastCol, row); - Console.Write (output); - } - - foreach (var s in Application.Sixel) - { - if (!string.IsNullOrWhiteSpace (s.SixelData)) - { - SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y); - Console.Write (s.SixelData); - } - } - } - - SetCursorPosition (0, 0); - - _cachedCursorVisibility = savedVisibility; - - void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) - { - SetCursorPosition (lastCol, row); - Console.Write (output); - output.Clear (); - lastCol += outputWidth; - outputWidth = 0; - } - } - - internal override void End () - { - if (IsWinPlatform) - { - NetWinConsole?.Cleanup (); - } - - StopReportingMouseMoves (); - - _ansiResponseTokenSource?.Cancel (); - _ansiResponseTokenSource?.Dispose (); - - _waitAnsiResponse?.Dispose (); - - if (!RunningUnitTests) - { - Console.ResetColor (); - - //Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); - - //Set cursor key to cursor. - Console.Out.Write (EscSeqUtils.CSI_ShowCursor); - Console.Out.Close (); - } - } - - internal override MainLoop Init () - { - PlatformID p = Environment.OSVersion.Platform; - - if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) - { - IsWinPlatform = true; - - try - { - NetWinConsole = new NetWinVTConsole (); - } - catch (ApplicationException) - { - // Likely running as a unit test, or in a non-interactive session. - } - } - - if (IsWinPlatform) - { - Clipboard = new WindowsClipboard (); - } - else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) - { - Clipboard = new MacOSXClipboard (); - } - else - { - if (CursesDriver.Is_WSL_Platform ()) - { - Clipboard = new WSLClipboard (); - } - else - { - Clipboard = new CursesClipboard (); - } - } - - if (!RunningUnitTests) - { - Console.TreatControlCAsInput = true; - - Cols = Console.WindowWidth; - Rows = Console.WindowHeight; - - //Enable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); - - //Set cursor key to application. - Console.Out.Write (EscSeqUtils.CSI_HideCursor); - } - else - { - // We are being run in an environment that does not support a console - // such as a unit test, or a pipe. - Cols = 80; - Rows = 24; - } - - ResizeScreen (); - ClearContents (); - CurrentAttribute = new Attribute (Color.White, Color.Black); - - StartReportingMouseMoves (); - - _mainLoopDriver = new NetMainLoop (this); - _mainLoopDriver.ProcessInput = ProcessInput; - - - return new MainLoop (_mainLoopDriver); - } - - private void ProcessInput (InputResult inputEvent) - { - switch (inputEvent.EventType) - { - case EventType.Key: - ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo; - - //if (consoleKeyInfo.Key == ConsoleKey.Packet) { - // consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); - //} - - //Debug.WriteLine ($"event: {inputEvent}"); - - KeyCode map = MapKey (consoleKeyInfo); - - if (map == KeyCode.Null) - { - break; - } - - OnKeyDown (new Key (map)); - OnKeyUp (new Key (map)); - - break; - case EventType.Mouse: - MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent); - //Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}"); - OnMouseEvent (me); - - break; - case EventType.WindowSize: - _winSizeChanging = true; - Top = 0; - Left = 0; - Cols = inputEvent.WindowSizeEvent.Size.Width; - Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); - ; - ResizeScreen (); - ClearContents (); - _winSizeChanging = false; - OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); - - break; - case EventType.RequestResponse: - break; - case EventType.WindowPosition: - break; - default: - throw new ArgumentOutOfRangeException (); - } - } - - #region Size and Position Handling - - private volatile bool _winSizeChanging; - - private void SetWindowPosition (int col, int row) - { - if (!RunningUnitTests) - { - Top = Console.WindowTop; - Left = Console.WindowLeft; - } - else - { - Top = row; - Left = col; - } - } - - public virtual void ResizeScreen () - { - // Not supported on Unix. - if (IsWinPlatform) - { - // Can raise an exception while is still resizing. - try - { -#pragma warning disable CA1416 - if (Console.WindowHeight > 0) - { - Console.CursorTop = 0; - Console.CursorLeft = 0; - Console.WindowTop = 0; - Console.WindowLeft = 0; - - if (Console.WindowHeight > Rows) - { - Console.SetWindowSize (Cols, Rows); - } - - Console.SetBufferSize (Cols, Rows); - } -#pragma warning restore CA1416 - } - // INTENT: Why are these eating the exceptions? - // Comments would be good here. - catch (IOException) - { - // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); - } - catch (ArgumentOutOfRangeException) - { - // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); - } - } - else - { - Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols)); - } - - // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); - } - - #endregion - - #region Color Handling - - // Cache the list of ConsoleColor values. - [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] - private static readonly HashSet ConsoleColorValues = new ( - Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (c => (int)c) - ); - - // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console. - private static readonly Dictionary colorMap = new () - { - { ConsoleColor.Black, COLOR_BLACK }, - { ConsoleColor.DarkBlue, COLOR_BLUE }, - { ConsoleColor.DarkGreen, COLOR_GREEN }, - { ConsoleColor.DarkCyan, COLOR_CYAN }, - { ConsoleColor.DarkRed, COLOR_RED }, - { ConsoleColor.DarkMagenta, COLOR_MAGENTA }, - { ConsoleColor.DarkYellow, COLOR_YELLOW }, - { ConsoleColor.Gray, COLOR_WHITE }, - { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK }, - { ConsoleColor.Blue, COLOR_BRIGHT_BLUE }, - { ConsoleColor.Green, COLOR_BRIGHT_GREEN }, - { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN }, - { ConsoleColor.Red, COLOR_BRIGHT_RED }, - { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA }, - { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW }, - { ConsoleColor.White, COLOR_BRIGHT_WHITE } - }; - - // Map a ConsoleColor to a platform dependent value. - private int MapColors (ConsoleColor color, bool isForeground = true) - { - return colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; - } - - ///// - ///// In the NetDriver, colors are encoded as an int. - ///// However, the foreground color is stored in the most significant 16 bits, - ///// and the background color is stored in the least significant 16 bits. - ///// - //public override Attribute MakeColor (Color foreground, Color background) - //{ - // // Encode the colors into the int value. - // return new Attribute ( - // platformColor: ((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff), - // foreground: foreground, - // background: background - // ); - //} - - #endregion - - #region Cursor Handling - - private bool SetCursorPosition (int col, int row) - { - if (IsWinPlatform) - { - // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. - try - { - Console.SetCursorPosition (col, row); - - return true; - } - catch (Exception) - { - return false; - } - } - - // + 1 is needed because non-Windows is based on 1 instead of 0 and - // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); - - return true; - } - - private CursorVisibility? _cachedCursorVisibility; - - public override void UpdateCursor () - { - EnsureCursorVisibility (); - - if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows) - { - SetCursorPosition (Col, Row); - SetWindowPosition (0, Row); - } - } - - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - visibility = _cachedCursorVisibility ?? CursorVisibility.Default; - - return visibility == CursorVisibility.Default; - } - - public override bool SetCursorVisibility (CursorVisibility visibility) - { - _cachedCursorVisibility = visibility; - - Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); - - return visibility == CursorVisibility.Default; - } - - public override bool EnsureCursorVisibility () - { - if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) - { - GetCursorVisibility (out CursorVisibility cursorVisibility); - _cachedCursorVisibility = cursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - - return false; - } - - SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default); - - return _cachedCursorVisibility == CursorVisibility.Default; - } - - #endregion - - #region Mouse Handling - - public void StartReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); - } - } - - public void StopReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); - } - } - - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private readonly CancellationTokenSource _ansiResponseTokenSource = new (); - - /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (_mainLoopDriver is null) - { - return string.Empty; - } - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.Response); - - _waitAnsiResponse.Set (); - }; - - _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); - - _mainLoopDriver._netEvents._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - _mainLoopDriver._netEvents._waitForStart.Set (); - - if (!_mainLoopDriver._waitForProbe.IsSet) - { - _mainLoopDriver._waitForProbe.Set (); - } - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return string.Empty; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._netEvents._forceRead = false; - - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) - { - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.Response)) - { - lock (request!.AnsiRequest._responseLock) - { - // Bad request or no response at all - _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.Response; - } - } - - /// - public override void WriteRaw (string ansi) { throw new NotImplementedException (); } - - private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me) - { - //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); - - MouseFlags mouseFlag = 0; - - if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0) - { - mouseFlag |= MouseFlags.Button1Pressed; - } - - if ((me.ButtonState & MouseButtonState.Button1Released) != 0) - { - mouseFlag |= MouseFlags.Button1Released; - } - - if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0) - { - mouseFlag |= MouseFlags.Button1Clicked; - } - - if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0) - { - mouseFlag |= MouseFlags.Button1DoubleClicked; - } - - if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0) - { - mouseFlag |= MouseFlags.Button1TripleClicked; - } - - if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0) - { - mouseFlag |= MouseFlags.Button2Pressed; - } - - if ((me.ButtonState & MouseButtonState.Button2Released) != 0) - { - mouseFlag |= MouseFlags.Button2Released; - } - - if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0) - { - mouseFlag |= MouseFlags.Button2Clicked; - } - - if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0) - { - mouseFlag |= MouseFlags.Button2DoubleClicked; - } - - if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0) - { - mouseFlag |= MouseFlags.Button2TripleClicked; - } - - if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0) - { - mouseFlag |= MouseFlags.Button3Pressed; - } - - if ((me.ButtonState & MouseButtonState.Button3Released) != 0) - { - mouseFlag |= MouseFlags.Button3Released; - } - - if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0) - { - mouseFlag |= MouseFlags.Button3Clicked; - } - - if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0) - { - mouseFlag |= MouseFlags.Button3DoubleClicked; - } - - if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0) - { - mouseFlag |= MouseFlags.Button3TripleClicked; - } - - if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0) - { - mouseFlag |= MouseFlags.WheeledUp; - } - - if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0) - { - mouseFlag |= MouseFlags.WheeledDown; - } - - if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0) - { - mouseFlag |= MouseFlags.WheeledLeft; - } - - if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0) - { - mouseFlag |= MouseFlags.WheeledRight; - } - - if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0) - { - mouseFlag |= MouseFlags.Button4Pressed; - } - - if ((me.ButtonState & MouseButtonState.Button4Released) != 0) - { - mouseFlag |= MouseFlags.Button4Released; - } - - if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0) - { - mouseFlag |= MouseFlags.Button4Clicked; - } - - if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0) - { - mouseFlag |= MouseFlags.Button4DoubleClicked; - } - - if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0) - { - mouseFlag |= MouseFlags.Button4TripleClicked; - } - - if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0) - { - mouseFlag |= MouseFlags.ReportMousePosition; - } - - if ((me.ButtonState & MouseButtonState.ButtonShift) != 0) - { - mouseFlag |= MouseFlags.ButtonShift; - } - - if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0) - { - mouseFlag |= MouseFlags.ButtonCtrl; - } - - if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0) - { - mouseFlag |= MouseFlags.ButtonAlt; - } - - return new MouseEventArgs { Position = me.Position, Flags = mouseFlag }; - } - - #endregion Mouse Handling - - #region Keyboard Handling - - private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) - { - if (consoleKeyInfo.Key != ConsoleKey.Packet) - { - return consoleKeyInfo; - } - - ConsoleModifiers mod = consoleKeyInfo.Modifiers; - bool shift = (mod & ConsoleModifiers.Shift) != 0; - bool alt = (mod & ConsoleModifiers.Alt) != 0; - bool control = (mod & ConsoleModifiers.Control) != 0; - - ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); - - return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); - } - - private KeyCode MapKey (ConsoleKeyInfo keyInfo) - { - switch (keyInfo.Key) - { - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: - case ConsoleKey.Packet: - case ConsoleKey.Oem1: - case ConsoleKey.Oem2: - case ConsoleKey.Oem3: - case ConsoleKey.Oem4: - case ConsoleKey.Oem5: - case ConsoleKey.Oem6: - case ConsoleKey.Oem7: - case ConsoleKey.Oem8: - case ConsoleKey.Oem102: - if (keyInfo.KeyChar == 0) - { - // If the keyChar is 0, keyInfo.Key value is not a printable character. - - return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); - } - - if (keyInfo.Modifiers != ConsoleModifiers.Shift) - { - // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); - } - - // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç") - // and passing on Shift would be redundant. - return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); - } - - // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC - if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) - { - if (keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I) - { - return KeyCode.Tab; - } - - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key)); - } - - // Handle control keys (e.g. CursorUp) - if (keyInfo.Key != ConsoleKey.None - && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)) - { - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); - } - - if (((ConsoleKey)keyInfo.KeyChar) is >= ConsoleKey.A and <= ConsoleKey.Z) - { - // Shifted - keyInfo = new ConsoleKeyInfo ( - keyInfo.KeyChar, - (ConsoleKey)keyInfo.KeyChar, - true, - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); - } - - if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z) - { - // Unshifted - keyInfo = new ConsoleKeyInfo ( - keyInfo.KeyChar, - (ConsoleKey)(keyInfo.KeyChar - 32), - false, - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); - } - - if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z ) - { - if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) - || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) - { - // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos - return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key); - } - - if (keyInfo.Modifiers == ConsoleModifiers.Shift) - { - // If ShiftMask is on add the ShiftMask - if (char.IsUpper (keyInfo.KeyChar)) - { - return (KeyCode)keyInfo.Key | KeyCode.ShiftMask; - } - } - - return (KeyCode)keyInfo.Key; - } - - - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar)); - } - - #endregion Keyboard Handling -} - -/// -/// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is -/// cross-platform but lacks things like file descriptor monitoring. -/// -/// This implementation is used for NetDriver. -internal class NetMainLoop : IMainLoopDriver -{ - internal NetEvents _netEvents; - - /// Invoked when a Key is pressed. - internal Action ProcessInput; - - private readonly ManualResetEventSlim _eventReady = new (false); - private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - private readonly ConcurrentQueue _resultQueue = new (); - internal readonly ManualResetEventSlim _waitForProbe = new (false); - private readonly CancellationTokenSource _eventReadyTokenSource = new (); - private MainLoop _mainLoop; - - /// Initializes the class with the console driver. - /// Passing a consoleDriver is provided to capture windows resizing. - /// The console driver used by this Net main loop. - /// - public NetMainLoop (ConsoleDriver consoleDriver = null) - { - if (consoleDriver is null) - { - throw new ArgumentNullException (nameof (consoleDriver)); - } - - _netEvents = new NetEvents (consoleDriver); - } - - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - _mainLoop = mainLoop; - - if (ConsoleDriver.RunningUnitTests) - { - return; - } - - Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); - } - - void IMainLoopDriver.Wakeup () { _eventReady.Set (); } - - bool IMainLoopDriver.EventsPending () - { - _waitForProbe.Set (); - - if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) - { - return true; - } - - try - { - if (!_eventReadyTokenSource.IsCancellationRequested) - { - // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there - // are no timers, but there IS an idle handler waiting. - _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return true; - } - finally - { - _eventReady.Reset (); - } - - _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); - - if (!_eventReadyTokenSource.IsCancellationRequested) - { - return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); - } - - return true; - } - - void IMainLoopDriver.Iteration () - { - while (_resultQueue.Count > 0) - { - // Always dequeue even if it's null and invoke if isn't null - if (_resultQueue.TryDequeue (out InputResult? dequeueResult)) - { - if (dequeueResult is { }) - { - ProcessInput?.Invoke (dequeueResult.Value); - } - } - } - } - - void IMainLoopDriver.TearDown () - { - _inputHandlerTokenSource?.Cancel (); - _inputHandlerTokenSource?.Dispose (); - _eventReadyTokenSource?.Cancel (); - _eventReadyTokenSource?.Dispose (); - - _eventReady?.Dispose (); - - _resultQueue?.Clear (); - _waitForProbe?.Dispose (); - _netEvents?.Dispose (); - _netEvents = null; - - _mainLoop = null; - } - - private void NetInputHandler () - { - while (_mainLoop is { }) - { - try - { - if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested) - { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return; - } - finally - { - if (_waitForProbe.IsSet) - { - _waitForProbe.Reset (); - } - } - - if (_inputHandlerTokenSource.IsCancellationRequested) - { - return; - } - - _inputHandlerTokenSource.Token.ThrowIfCancellationRequested (); - - if (_resultQueue.Count == 0) - { - _resultQueue.Enqueue (_netEvents.DequeueInput ()); - } - - try - { - while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out InputResult? result) && result is null) - { - // Dequeue null values - _resultQueue.TryDequeue (out _); - } - } - catch (InvalidOperationException) // Peek can raise an exception - { } - - if (_resultQueue.Count > 0) - { - _eventReady.Set (); - } - } - } -} diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs new file mode 100644 index 000000000..e4103d8c3 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -0,0 +1,965 @@ +// TODO: #nullable enable +// +// NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. +// + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; +using static Terminal.Gui.NetEvents; + +namespace Terminal.Gui; + +internal class NetDriver : ConsoleDriver +{ + public bool IsWinPlatform { get; private set; } + public NetWinVTConsole NetWinConsole { get; private set; } + + public override void Refresh () + { + UpdateScreen (); + UpdateCursor (); + } + + public override void Suspend () + { + if (Environment.OSVersion.Platform != PlatformID.Unix) + { + return; + } + + StopReportingMouseMoves (); + + if (!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); + + SetContentsAsDirty (); + Refresh (); + } + + StartReportingMouseMoves (); + } + + #region Screen and Contents + + public override void UpdateScreen () + { + if (RunningUnitTests + || _winSizeChanging + || Console.WindowHeight < 1 + || Contents.Length != Rows * Cols + || Rows != Console.WindowHeight) + { + return; + } + + var top = 0; + var left = 0; + int rows = Rows; + int cols = Cols; + var output = new StringBuilder (); + Attribute? redrawAttr = null; + int lastCol = -1; + + CursorVisibility? savedVisibility = _cachedCursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + for (int row = top; row < rows; row++) + { + if (Console.WindowHeight < 1) + { + return; + } + + if (!_dirtyLines [row]) + { + continue; + } + + if (!SetCursorPosition (0, row)) + { + return; + } + + _dirtyLines [row] = false; + output.Clear (); + + for (int col = left; col < cols; col++) + { + lastCol = -1; + var outputWidth = 0; + + for (; col < cols; col++) + { + if (!Contents [row, col].IsDirty) + { + if (output.Length > 0) + { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + } + else if (lastCol == -1) + { + lastCol = col; + } + + if (lastCol + 1 < cols) + { + lastCol++; + } + + continue; + } + + if (lastCol == -1) + { + lastCol = col; + } + + Attribute attr = Contents [row, col].Attribute.Value; + + // Performance: Only send the escape sequence if the attribute has changed. + if (attr != redrawAttr) + { + redrawAttr = attr; + + if (Force16Colors) + { + output.Append ( + EscSeqUtils.CSI_SetGraphicsRendition ( + MapColors ( + (ConsoleColor)attr.Background.GetClosestNamedColor16 (), + false + ), + MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ()) + ) + ); + } + else + { + output.Append ( + EscSeqUtils.CSI_SetForegroundColorRGB ( + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ) + ); + + output.Append ( + EscSeqUtils.CSI_SetBackgroundColorRGB ( + attr.Background.R, + attr.Background.G, + attr.Background.B + ) + ); + } + } + + outputWidth++; + Rune rune = Contents [row, col].Rune; + output.Append (rune); + + if (Contents [row, col].CombiningMarks.Count > 0) + { + // AtlasEngine does not support NON-NORMALIZED combining marks in a way + // compatible with the driver architecture. Any CMs (except in the first col) + // are correctly combined with the base char, but are ALSO treated as 1 column + // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. + // + // For now, we just ignore the list of CMs. + //foreach (var combMark in Contents [row, col].CombiningMarks) { + // output.Append (combMark); + //} + // WriteToConsole (output, ref lastCol, row, ref outputWidth); + } + else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) + { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + SetCursorPosition (col - 1, row); + } + + Contents [row, col].IsDirty = false; + } + } + + if (output.Length > 0) + { + SetCursorPosition (lastCol, row); + Console.Write (output); + } + + foreach (SixelToRender s in Application.Sixel) + { + if (!string.IsNullOrWhiteSpace (s.SixelData)) + { + SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y); + Console.Write (s.SixelData); + } + } + } + + SetCursorPosition (0, 0); + + _cachedCursorVisibility = savedVisibility; + + void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) + { + SetCursorPosition (lastCol, row); + Console.Write (output); + output.Clear (); + lastCol += outputWidth; + outputWidth = 0; + } + } + + #endregion Screen and Contents + + #region Init/End/MainLoop + + internal NetMainLoop _mainLoopDriver; + + internal override MainLoop Init () + { + PlatformID p = Environment.OSVersion.Platform; + + if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) + { + IsWinPlatform = true; + + try + { + NetWinConsole = new (); + } + catch (ApplicationException) + { + // Likely running as a unit test, or in a non-interactive session. + } + } + + if (IsWinPlatform) + { + Clipboard = new WindowsClipboard (); + } + else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) + { + Clipboard = new MacOSXClipboard (); + } + else + { + if (CursesDriver.Is_WSL_Platform ()) + { + Clipboard = new WSLClipboard (); + } + else + { + Clipboard = new CursesClipboard (); + } + } + + if (!RunningUnitTests) + { + Console.TreatControlCAsInput = true; + + Cols = Console.WindowWidth; + Rows = Console.WindowHeight; + + //Enable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + + //Set cursor key to application. + Console.Out.Write (EscSeqUtils.CSI_HideCursor); + } + else + { + // We are being run in an environment that does not support a console + // such as a unit test, or a pipe. + Cols = 80; + Rows = 24; + } + + ResizeScreen (); + ClearContents (); + CurrentAttribute = new (Color.White, Color.Black); + + StartReportingMouseMoves (); + + _mainLoopDriver = new (this); + _mainLoopDriver.ProcessInput = ProcessInput; + + return new (_mainLoopDriver); + } + + private void ProcessInput (InputResult inputEvent) + { + switch (inputEvent.EventType) + { + case EventType.Key: + ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo; + + //if (consoleKeyInfo.Key == ConsoleKey.Packet) { + // consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); + //} + + //Debug.WriteLine ($"event: {inputEvent}"); + + KeyCode map = MapKey (consoleKeyInfo); + + if (map == KeyCode.Null) + { + break; + } + + OnKeyDown (new (map)); + OnKeyUp (new (map)); + + break; + case EventType.Mouse: + MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent); + + //Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}"); + OnMouseEvent (me); + + break; + case EventType.WindowSize: + _winSizeChanging = true; + Top = 0; + Left = 0; + Cols = inputEvent.WindowSizeEvent.Size.Width; + Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); + ; + ResizeScreen (); + ClearContents (); + _winSizeChanging = false; + OnSizeChanged (new (new (Cols, Rows))); + + break; + case EventType.RequestResponse: + break; + case EventType.WindowPosition: + break; + default: + throw new ArgumentOutOfRangeException (); + } + } + + internal override void End () + { + if (IsWinPlatform) + { + NetWinConsole?.Cleanup (); + } + + StopReportingMouseMoves (); + + _ansiResponseTokenSource?.Cancel (); + _ansiResponseTokenSource?.Dispose (); + + _waitAnsiResponse?.Dispose (); + + if (!RunningUnitTests) + { + Console.ResetColor (); + + //Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + + //Set cursor key to cursor. + Console.Out.Write (EscSeqUtils.CSI_ShowCursor); + Console.Out.Close (); + } + } + + #endregion Init/End/MainLoop + + #region Color Handling + + public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix + || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931); + + private const int COLOR_BLACK = 30; + private const int COLOR_BLUE = 34; + private const int COLOR_BRIGHT_BLACK = 90; + private const int COLOR_BRIGHT_BLUE = 94; + private const int COLOR_BRIGHT_CYAN = 96; + private const int COLOR_BRIGHT_GREEN = 92; + private const int COLOR_BRIGHT_MAGENTA = 95; + private const int COLOR_BRIGHT_RED = 91; + private const int COLOR_BRIGHT_WHITE = 97; + private const int COLOR_BRIGHT_YELLOW = 93; + private const int COLOR_CYAN = 36; + private const int COLOR_GREEN = 32; + private const int COLOR_MAGENTA = 35; + private const int COLOR_RED = 31; + private const int COLOR_WHITE = 37; + private const int COLOR_YELLOW = 33; + + // Cache the list of ConsoleColor values. + [UnconditionalSuppressMessage ( + "AOT", + "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", + Justification = "")] + private static readonly HashSet ConsoleColorValues = new ( + Enum.GetValues (typeof (ConsoleColor)) + .OfType () + .Select (c => (int)c) + ); + + // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console. + private static readonly Dictionary colorMap = new () + { + { ConsoleColor.Black, COLOR_BLACK }, + { ConsoleColor.DarkBlue, COLOR_BLUE }, + { ConsoleColor.DarkGreen, COLOR_GREEN }, + { ConsoleColor.DarkCyan, COLOR_CYAN }, + { ConsoleColor.DarkRed, COLOR_RED }, + { ConsoleColor.DarkMagenta, COLOR_MAGENTA }, + { ConsoleColor.DarkYellow, COLOR_YELLOW }, + { ConsoleColor.Gray, COLOR_WHITE }, + { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK }, + { ConsoleColor.Blue, COLOR_BRIGHT_BLUE }, + { ConsoleColor.Green, COLOR_BRIGHT_GREEN }, + { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN }, + { ConsoleColor.Red, COLOR_BRIGHT_RED }, + { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA }, + { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW }, + { ConsoleColor.White, COLOR_BRIGHT_WHITE } + }; + + // Map a ConsoleColor to a platform dependent value. + private int MapColors (ConsoleColor color, bool isForeground = true) + { + return colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; + } + + #endregion + + #region Cursor Handling + + private bool SetCursorPosition (int col, int row) + { + if (IsWinPlatform) + { + // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. + try + { + Console.SetCursorPosition (col, row); + + return true; + } + catch (Exception) + { + return false; + } + } + + // + 1 is needed because non-Windows is based on 1 instead of 0 and + // Console.CursorTop/CursorLeft isn't reliable. + Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); + + return true; + } + + private CursorVisibility? _cachedCursorVisibility; + + public override void UpdateCursor () + { + EnsureCursorVisibility (); + + if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows) + { + SetCursorPosition (Col, Row); + SetWindowPosition (0, Row); + } + } + + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + visibility = _cachedCursorVisibility ?? CursorVisibility.Default; + + return visibility == CursorVisibility.Default; + } + + public override bool SetCursorVisibility (CursorVisibility visibility) + { + _cachedCursorVisibility = visibility; + + Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + + return visibility == CursorVisibility.Default; + } + + public override bool EnsureCursorVisibility () + { + if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) + { + GetCursorVisibility (out CursorVisibility cursorVisibility); + _cachedCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + return false; + } + + SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default); + + return _cachedCursorVisibility == CursorVisibility.Default; + } + + #endregion + + #region Mouse Handling + + public void StartReportingMouseMoves () + { + if (!RunningUnitTests) + { + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + } + } + + public void StopReportingMouseMoves () + { + if (!RunningUnitTests) + { + Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + } + } + + private MouseEventArgs ToDriverMouse (MouseEvent me) + { + //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); + + MouseFlags mouseFlag = 0; + + if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0) + { + mouseFlag |= MouseFlags.Button1Pressed; + } + + if ((me.ButtonState & MouseButtonState.Button1Released) != 0) + { + mouseFlag |= MouseFlags.Button1Released; + } + + if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0) + { + mouseFlag |= MouseFlags.Button1Clicked; + } + + if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0) + { + mouseFlag |= MouseFlags.Button1DoubleClicked; + } + + if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0) + { + mouseFlag |= MouseFlags.Button1TripleClicked; + } + + if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0) + { + mouseFlag |= MouseFlags.Button2Pressed; + } + + if ((me.ButtonState & MouseButtonState.Button2Released) != 0) + { + mouseFlag |= MouseFlags.Button2Released; + } + + if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0) + { + mouseFlag |= MouseFlags.Button2Clicked; + } + + if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0) + { + mouseFlag |= MouseFlags.Button2DoubleClicked; + } + + if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0) + { + mouseFlag |= MouseFlags.Button2TripleClicked; + } + + if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0) + { + mouseFlag |= MouseFlags.Button3Pressed; + } + + if ((me.ButtonState & MouseButtonState.Button3Released) != 0) + { + mouseFlag |= MouseFlags.Button3Released; + } + + if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0) + { + mouseFlag |= MouseFlags.Button3Clicked; + } + + if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0) + { + mouseFlag |= MouseFlags.Button3DoubleClicked; + } + + if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0) + { + mouseFlag |= MouseFlags.Button3TripleClicked; + } + + if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0) + { + mouseFlag |= MouseFlags.WheeledUp; + } + + if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0) + { + mouseFlag |= MouseFlags.WheeledDown; + } + + if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0) + { + mouseFlag |= MouseFlags.WheeledLeft; + } + + if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0) + { + mouseFlag |= MouseFlags.WheeledRight; + } + + if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0) + { + mouseFlag |= MouseFlags.Button4Pressed; + } + + if ((me.ButtonState & MouseButtonState.Button4Released) != 0) + { + mouseFlag |= MouseFlags.Button4Released; + } + + if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0) + { + mouseFlag |= MouseFlags.Button4Clicked; + } + + if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0) + { + mouseFlag |= MouseFlags.Button4DoubleClicked; + } + + if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0) + { + mouseFlag |= MouseFlags.Button4TripleClicked; + } + + if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0) + { + mouseFlag |= MouseFlags.ReportMousePosition; + } + + if ((me.ButtonState & MouseButtonState.ButtonShift) != 0) + { + mouseFlag |= MouseFlags.ButtonShift; + } + + if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0) + { + mouseFlag |= MouseFlags.ButtonCtrl; + } + + if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0) + { + mouseFlag |= MouseFlags.ButtonAlt; + } + + return new() { Position = me.Position, Flags = mouseFlag }; + } + + #endregion Mouse Handling + + #region Keyboard Handling + + public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) + { + var input = new InputResult + { + EventType = EventType.Key, ConsoleKeyInfo = new (keyChar, key, shift, alt, control) + }; + + try + { + ProcessInput (input); + } + catch (OverflowException) + { } + } + + private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + if (consoleKeyInfo.Key != ConsoleKey.Packet) + { + return consoleKeyInfo; + } + + ConsoleModifiers mod = consoleKeyInfo.Modifiers; + bool shift = (mod & ConsoleModifiers.Shift) != 0; + bool alt = (mod & ConsoleModifiers.Alt) != 0; + bool control = (mod & ConsoleModifiers.Control) != 0; + + ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); + + return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); + } + + private KeyCode MapKey (ConsoleKeyInfo keyInfo) + { + switch (keyInfo.Key) + { + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + case ConsoleKey.Packet: + case ConsoleKey.Oem1: + case ConsoleKey.Oem2: + case ConsoleKey.Oem3: + case ConsoleKey.Oem4: + case ConsoleKey.Oem5: + case ConsoleKey.Oem6: + case ConsoleKey.Oem7: + case ConsoleKey.Oem8: + case ConsoleKey.Oem102: + if (keyInfo.KeyChar == 0) + { + // If the keyChar is 0, keyInfo.Key value is not a printable character. + + return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); + } + + if (keyInfo.Modifiers != ConsoleModifiers.Shift) + { + // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); + } + + // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç") + // and passing on Shift would be redundant. + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); + } + + // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC + if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) + { + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I) + { + return KeyCode.Tab; + } + + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); + } + + // Handle control keys (e.g. CursorUp) + if (keyInfo.Key != ConsoleKey.None + && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)) + { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); + } + + if ((ConsoleKey)keyInfo.KeyChar is >= ConsoleKey.A and <= ConsoleKey.Z) + { + // Shifted + keyInfo = new ( + keyInfo.KeyChar, + (ConsoleKey)keyInfo.KeyChar, + true, + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); + } + + if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z) + { + // Unshifted + keyInfo = new ( + keyInfo.KeyChar, + (ConsoleKey)(keyInfo.KeyChar - 32), + false, + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); + } + + if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) + { + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) + || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) + { + // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key); + } + + if (keyInfo.Modifiers == ConsoleModifiers.Shift) + { + // If ShiftMask is on add the ShiftMask + if (char.IsUpper (keyInfo.KeyChar)) + { + return (KeyCode)keyInfo.Key | KeyCode.ShiftMask; + } + } + + return (KeyCode)keyInfo.Key; + } + + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); + } + + #endregion Keyboard Handling + + #region Low-Level DotNet tuff + + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + + /// + public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + { + if (_mainLoopDriver is null) + { + return string.Empty; + } + + try + { + lock (ansiRequest._responseLock) + { + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == ansiRequest); + Debug.Assert (e == ansiRequest.Response); + + _waitAnsiResponse.Set (); + }; + + _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); + + _mainLoopDriver._netEvents._forceRead = true; + } + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + _mainLoopDriver._netEvents._waitForStart.Set (); + + if (!_mainLoopDriver._waitForProbe.IsSet) + { + _mainLoopDriver._waitForProbe.Set (); + } + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return string.Empty; + } + + lock (ansiRequest._responseLock) + { + _mainLoopDriver._netEvents._forceRead = false; + + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + { + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.Response)) + { + lock (request!.AnsiRequest._responseLock) + { + // Bad request or no response at all + _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); + } + } + } + + _waitAnsiResponse.Reset (); + + return ansiRequest.Response; + } + } + + /// + public override void WriteRaw (string ansi) { throw new NotImplementedException (); } + + private volatile bool _winSizeChanging; + + private void SetWindowPosition (int col, int row) + { + if (!RunningUnitTests) + { + Top = Console.WindowTop; + Left = Console.WindowLeft; + } + else + { + Top = row; + Left = col; + } + } + + private void ResizeScreen () + { + // Not supported on Unix. + if (IsWinPlatform) + { + // Can raise an exception while is still resizing. + try + { +#pragma warning disable CA1416 + if (Console.WindowHeight > 0) + { + Console.CursorTop = 0; + Console.CursorLeft = 0; + Console.WindowTop = 0; + Console.WindowLeft = 0; + + if (Console.WindowHeight > Rows) + { + Console.SetWindowSize (Cols, Rows); + } + + Console.SetBufferSize (Cols, Rows); + } +#pragma warning restore CA1416 + } + + // INTENT: Why are these eating the exceptions? + // Comments would be good here. + catch (IOException) + { + // CONCURRENCY: Unsynchronized access to Clip is not safe. + Clip = new (0, 0, Cols, Rows); + } + catch (ArgumentOutOfRangeException) + { + // CONCURRENCY: Unsynchronized access to Clip is not safe. + Clip = new (0, 0, Cols, Rows); + } + } + else + { + Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols)); + } + + // CONCURRENCY: Unsynchronized access to Clip is not safe. + Clip = new (0, 0, Cols, Rows); + } + + #endregion Low-Level DotNet tuff +} diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs new file mode 100644 index 000000000..939ed0d2d --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -0,0 +1,753 @@ +// TODO: #nullable enable +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +namespace Terminal.Gui; + +internal class NetEvents : IDisposable +{ + private readonly ManualResetEventSlim _inputReady = new (false); + private CancellationTokenSource _inputReadyCancellationTokenSource; + internal readonly ManualResetEventSlim _waitForStart = new (false); + + //CancellationTokenSource _waitForStartCancellationTokenSource; + private readonly ManualResetEventSlim _winChange = new (false); + private readonly ConcurrentQueue _inputQueue = new (); + private readonly ConsoleDriver _consoleDriver; + private ConsoleKeyInfo [] _cki; + private bool _isEscSeq; +#if PROCESS_REQUEST + bool _neededProcessRequest; +#endif + public EscSeqRequests EscSeqRequests { get; } = new (); + + public NetEvents (ConsoleDriver consoleDriver) + { + _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); + _inputReadyCancellationTokenSource = new CancellationTokenSource (); + + Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); + + Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token); + } + + public InputResult? DequeueInput () + { + while (_inputReadyCancellationTokenSource != null + && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested) + { + _waitForStart.Set (); + _winChange.Set (); + + try + { + if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) + { + if (_inputQueue.Count == 0) + { + _inputReady.Wait (_inputReadyCancellationTokenSource.Token); + } + } + } + catch (OperationCanceledException) + { + return null; + } + finally + { + _inputReady.Reset (); + } + +#if PROCESS_REQUEST + _neededProcessRequest = false; +#endif + if (_inputQueue.Count > 0) + { + if (_inputQueue.TryDequeue (out InputResult? result)) + { + return result; + } + } + } + + return null; + } + + private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true) + { + while (!cancellationToken.IsCancellationRequested) + { + // if there is a key available, return it without waiting + // (or dispatching work to the thread queue) + if (Console.KeyAvailable) + { + return Console.ReadKey (intercept); + } + + if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + { + if (_retries > 1) + { + if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + { + lock (seqReqStatus!.AnsiRequest._responseLock) + { + EscSeqRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + + if (!_forceRead) + { + Task.Delay (100, cancellationToken).Wait (cancellationToken); + } + } + + cancellationToken.ThrowIfCancellationRequested (); + + return default (ConsoleKeyInfo); + } + + internal bool _forceRead; + private int _retries; + + private void ProcessInputQueue () + { + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + try + { + if (!_forceRead) + { + _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return; + } + + _waitForStart.Reset (); + + if (_inputQueue.Count == 0 || _forceRead) + { + ConsoleKey key = 0; + ConsoleModifiers mod = 0; + ConsoleKeyInfo newConsoleKeyInfo = default; + + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + ConsoleKeyInfo consoleKeyInfo; + + try + { + consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + if (EscSeqUtils.IncompleteCkInfos is { }) + { + EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki); + } + + if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) + || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) + { + if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) + { + _cki = EscSeqUtils.ResizeArray ( + new ConsoleKeyInfo ( + (char)KeyCode.Esc, + 0, + false, + false, + false + ), + _cki + ); + } + + _isEscSeq = true; + + if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) + || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))) + { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + else + { + newConsoleKeyInfo = consoleKeyInfo; + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + + if (Console.KeyAvailable) + { + continue; + } + + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + } + + break; + } + + if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) + { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + + if (Console.KeyAvailable) + { + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + } + else + { + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + + break; + } + + ProcessMapConsoleKeyInfo (consoleKeyInfo); + + if (_retries > 0) + { + _retries = 0; + } + + break; + } + } + + _inputReady.Set (); + } + + void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + _inputQueue.Enqueue ( + new InputResult + { + EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo) + } + ); + _isEscSeq = false; + } + } + + private void CheckWindowSizeChange () + { + void RequestWindowSize (CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + // Wait for a while then check if screen has changed sizes + Task.Delay (500, cancellationToken).Wait (cancellationToken); + + int buffHeight, buffWidth; + + if (((NetDriver)_consoleDriver).IsWinPlatform) + { + buffHeight = Math.Max (Console.BufferHeight, 0); + buffWidth = Math.Max (Console.BufferWidth, 0); + } + else + { + buffHeight = _consoleDriver.Rows; + buffWidth = _consoleDriver.Cols; + } + + if (EnqueueWindowSizeEvent ( + Math.Max (Console.WindowHeight, 0), + Math.Max (Console.WindowWidth, 0), + buffHeight, + buffWidth + )) + { + return; + } + } + + cancellationToken.ThrowIfCancellationRequested (); + } + + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + try + { + _winChange.Wait (_inputReadyCancellationTokenSource.Token); + _winChange.Reset (); + + RequestWindowSize (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + _inputReady.Set (); + } + } + + /// Enqueue a window size event if the window size has changed. + /// + /// + /// + /// + /// + private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth) + { + if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) + { + return false; + } + + int w = Math.Max (winWidth, 0); + int h = Math.Max (winHeight, 0); + + _inputQueue.Enqueue ( + new InputResult + { + EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) } + } + ); + + return true; + } + + // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event) + private void ProcessRequestResponse ( + ref ConsoleKeyInfo newConsoleKeyInfo, + ref ConsoleKey key, + ConsoleKeyInfo [] cki, + ref ConsoleModifiers mod + ) + { + // isMouse is true if it's CSI<, false otherwise + EscSeqUtils.DecodeEscSeq ( + EscSeqRequests, + ref newConsoleKeyInfo, + ref key, + cki, + ref mod, + out string c1Control, + out string code, + out string [] values, + out string terminating, + out bool isMouse, + out List mouseFlags, + out Point pos, + out EscSeqReqStatus seqReqStatus, + (f, p) => HandleMouseEvent (MapMouseFlags (f), p) + ); + + if (isMouse) + { + foreach (MouseFlags mf in mouseFlags) + { + HandleMouseEvent (MapMouseFlags (mf), pos); + } + + return; + } + + if (seqReqStatus is { }) + { + //HandleRequestResponseEvent (c1Control, code, values, terminating); + + var ckiString = EscSeqUtils.ToString (cki); + + lock (seqReqStatus.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = ckiString; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString); + } + + return; + } + + if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator)) + { + if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result)) + { + lock (result.AnsiRequest._responseLock) + { + result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator; + result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator); + + EscSeqUtils.InvalidRequestTerminator = null; + } + } + + return; + } + + if (newConsoleKeyInfo != default) + { + HandleKeyboardEvent (newConsoleKeyInfo); + } + } + + [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] + private MouseButtonState MapMouseFlags (MouseFlags mouseFlags) + { + MouseButtonState mbs = default; + + foreach (object flag in Enum.GetValues (mouseFlags.GetType ())) + { + if (mouseFlags.HasFlag ((MouseFlags)flag)) + { + switch (flag) + { + case MouseFlags.Button1Pressed: + mbs |= MouseButtonState.Button1Pressed; + + break; + case MouseFlags.Button1Released: + mbs |= MouseButtonState.Button1Released; + + break; + case MouseFlags.Button1Clicked: + mbs |= MouseButtonState.Button1Clicked; + + break; + case MouseFlags.Button1DoubleClicked: + mbs |= MouseButtonState.Button1DoubleClicked; + + break; + case MouseFlags.Button1TripleClicked: + mbs |= MouseButtonState.Button1TripleClicked; + + break; + case MouseFlags.Button2Pressed: + mbs |= MouseButtonState.Button2Pressed; + + break; + case MouseFlags.Button2Released: + mbs |= MouseButtonState.Button2Released; + + break; + case MouseFlags.Button2Clicked: + mbs |= MouseButtonState.Button2Clicked; + + break; + case MouseFlags.Button2DoubleClicked: + mbs |= MouseButtonState.Button2DoubleClicked; + + break; + case MouseFlags.Button2TripleClicked: + mbs |= MouseButtonState.Button2TripleClicked; + + break; + case MouseFlags.Button3Pressed: + mbs |= MouseButtonState.Button3Pressed; + + break; + case MouseFlags.Button3Released: + mbs |= MouseButtonState.Button3Released; + + break; + case MouseFlags.Button3Clicked: + mbs |= MouseButtonState.Button3Clicked; + + break; + case MouseFlags.Button3DoubleClicked: + mbs |= MouseButtonState.Button3DoubleClicked; + + break; + case MouseFlags.Button3TripleClicked: + mbs |= MouseButtonState.Button3TripleClicked; + + break; + case MouseFlags.WheeledUp: + mbs |= MouseButtonState.ButtonWheeledUp; + + break; + case MouseFlags.WheeledDown: + mbs |= MouseButtonState.ButtonWheeledDown; + + break; + case MouseFlags.WheeledLeft: + mbs |= MouseButtonState.ButtonWheeledLeft; + + break; + case MouseFlags.WheeledRight: + mbs |= MouseButtonState.ButtonWheeledRight; + + break; + case MouseFlags.Button4Pressed: + mbs |= MouseButtonState.Button4Pressed; + + break; + case MouseFlags.Button4Released: + mbs |= MouseButtonState.Button4Released; + + break; + case MouseFlags.Button4Clicked: + mbs |= MouseButtonState.Button4Clicked; + + break; + case MouseFlags.Button4DoubleClicked: + mbs |= MouseButtonState.Button4DoubleClicked; + + break; + case MouseFlags.Button4TripleClicked: + mbs |= MouseButtonState.Button4TripleClicked; + + break; + case MouseFlags.ButtonShift: + mbs |= MouseButtonState.ButtonShift; + + break; + case MouseFlags.ButtonCtrl: + mbs |= MouseButtonState.ButtonCtrl; + + break; + case MouseFlags.ButtonAlt: + mbs |= MouseButtonState.ButtonAlt; + + break; + case MouseFlags.ReportMousePosition: + mbs |= MouseButtonState.ReportMousePosition; + + break; + case MouseFlags.AllEvents: + mbs |= MouseButtonState.AllEvents; + + break; + } + } + } + + return mbs; + } + + private Point _lastCursorPosition; + + //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + //{ + // if (terminating == + + // // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. + // // The observation is correct because the response isn't immediate and this is useless + // EscSeqUtils.CSI_RequestCursorPositionReport.Terminator) + // { + // var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; + + // if (_lastCursorPosition.Y != point.Y) + // { + // _lastCursorPosition = point; + // var eventType = EventType.WindowPosition; + // var winPositionEv = new WindowPositionEvent { CursorPosition = point }; + + // _inputQueue.Enqueue ( + // new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } + // ); + // } + // else + // { + // return; + // } + // } + // else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator) + // { + // if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value) + // { + // EnqueueWindowSizeEvent ( + // Math.Max (int.Parse (values [1]), 0), + // Math.Max (int.Parse (values [2]), 0), + // Math.Max (int.Parse (values [1]), 0), + // Math.Max (int.Parse (values [2]), 0) + // ); + // } + // else + // { + // EnqueueRequestResponseEvent (c1Control, code, values, terminating); + // } + // } + // else + // { + // EnqueueRequestResponseEvent (c1Control, code, values, terminating); + // } + + // _inputReady.Set (); + //} + + //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + //{ + // var eventType = EventType.RequestResponse; + // var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) }; + + // _inputQueue.Enqueue ( + // new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv } + // ); + //} + + private void HandleMouseEvent (MouseButtonState buttonState, Point pos) + { + var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState }; + + _inputQueue.Enqueue ( + new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent } + ); + + _inputReady.Set (); + } + + public enum EventType + { + Key = 1, + Mouse = 2, + WindowSize = 3, + WindowPosition = 4, + RequestResponse = 5 + } + + [Flags] + public enum MouseButtonState + { + Button1Pressed = 0x1, + Button1Released = 0x2, + Button1Clicked = 0x4, + Button1DoubleClicked = 0x8, + Button1TripleClicked = 0x10, + Button2Pressed = 0x20, + Button2Released = 0x40, + Button2Clicked = 0x80, + Button2DoubleClicked = 0x100, + Button2TripleClicked = 0x200, + Button3Pressed = 0x400, + Button3Released = 0x800, + Button3Clicked = 0x1000, + Button3DoubleClicked = 0x2000, + Button3TripleClicked = 0x4000, + ButtonWheeledUp = 0x8000, + ButtonWheeledDown = 0x10000, + ButtonWheeledLeft = 0x20000, + ButtonWheeledRight = 0x40000, + Button4Pressed = 0x80000, + Button4Released = 0x100000, + Button4Clicked = 0x200000, + Button4DoubleClicked = 0x400000, + Button4TripleClicked = 0x800000, + ButtonShift = 0x1000000, + ButtonCtrl = 0x2000000, + ButtonAlt = 0x4000000, + ReportMousePosition = 0x8000000, + AllEvents = -1 + } + + public struct MouseEvent + { + public Point Position; + public MouseButtonState ButtonState; + } + + public struct WindowSizeEvent + { + public Size Size; + } + + public struct WindowPositionEvent + { + public int Top; + public int Left; + public Point CursorPosition; + } + + public struct RequestResponseEvent + { + public (string c1Control, string code, string [] values, string terminating) ResultTuple; + } + + public struct InputResult + { + public EventType EventType; + public ConsoleKeyInfo ConsoleKeyInfo; + public MouseEvent MouseEvent; + public WindowSizeEvent WindowSizeEvent; + public WindowPositionEvent WindowPositionEvent; + public RequestResponseEvent RequestResponseEvent; + + public readonly override string ToString () + { + return EventType switch + { + EventType.Key => ToString (ConsoleKeyInfo), + EventType.Mouse => MouseEvent.ToString (), + + //EventType.WindowSize => WindowSize.ToString (), + //EventType.RequestResponse => RequestResponse.ToString (), + _ => "Unknown event type: " + EventType + }; + } + + /// Prints a ConsoleKeyInfoEx structure + /// + /// + public readonly string ToString (ConsoleKeyInfo cki) + { + var ke = new Key ((KeyCode)cki.KeyChar); + var sb = new StringBuilder (); + sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})"); + sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); + sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) "); + string s = sb.ToString ().TrimEnd (',').TrimEnd (' '); + + return $"[ConsoleKeyInfo({s})]"; + } + } + + private void HandleKeyboardEvent (ConsoleKeyInfo cki) + { + var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki }; + + _inputQueue.Enqueue (inputResult); + } + + public void Dispose () + { + _inputReadyCancellationTokenSource?.Cancel (); + _inputReadyCancellationTokenSource?.Dispose (); + _inputReadyCancellationTokenSource = null; + + try + { + // throws away any typeahead that has been typed by + // the user and has not yet been read by the program. + while (Console.KeyAvailable) + { + Console.ReadKey (true); + } + } + catch (InvalidOperationException) + { + // Ignore - Console input has already been closed + } + } +} diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs new file mode 100644 index 000000000..d876ef0cb --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -0,0 +1,173 @@ +using System.Collections.Concurrent; + +namespace Terminal.Gui; + +/// +/// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is +/// cross-platform but lacks things like file descriptor monitoring. +/// +/// This implementation is used for NetDriver. +internal class NetMainLoop : IMainLoopDriver +{ + internal NetEvents _netEvents; + + /// Invoked when a Key is pressed. + internal Action ProcessInput; + + private readonly ManualResetEventSlim _eventReady = new (false); + private readonly CancellationTokenSource _inputHandlerTokenSource = new (); + private readonly ConcurrentQueue _resultQueue = new (); + internal readonly ManualResetEventSlim _waitForProbe = new (false); + private readonly CancellationTokenSource _eventReadyTokenSource = new (); + private MainLoop _mainLoop; + + /// Initializes the class with the console driver. + /// Passing a consoleDriver is provided to capture windows resizing. + /// The console driver used by this Net main loop. + /// + public NetMainLoop (ConsoleDriver consoleDriver = null) + { + if (consoleDriver is null) + { + throw new ArgumentNullException (nameof (consoleDriver)); + } + + _netEvents = new NetEvents (consoleDriver); + } + + void IMainLoopDriver.Setup (MainLoop mainLoop) + { + _mainLoop = mainLoop; + + if (ConsoleDriver.RunningUnitTests) + { + return; + } + + Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); + } + + void IMainLoopDriver.Wakeup () { _eventReady.Set (); } + + bool IMainLoopDriver.EventsPending () + { + _waitForProbe.Set (); + + if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) + { + return true; + } + + try + { + if (!_eventReadyTokenSource.IsCancellationRequested) + { + // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there + // are no timers, but there IS an idle handler waiting. + _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return true; + } + finally + { + _eventReady.Reset (); + } + + _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); + + if (!_eventReadyTokenSource.IsCancellationRequested) + { + return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); + } + + return true; + } + + void IMainLoopDriver.Iteration () + { + while (_resultQueue.Count > 0) + { + // Always dequeue even if it's null and invoke if isn't null + if (_resultQueue.TryDequeue (out NetEvents.InputResult? dequeueResult)) + { + if (dequeueResult is { }) + { + ProcessInput?.Invoke (dequeueResult.Value); + } + } + } + } + + void IMainLoopDriver.TearDown () + { + _inputHandlerTokenSource?.Cancel (); + _inputHandlerTokenSource?.Dispose (); + _eventReadyTokenSource?.Cancel (); + _eventReadyTokenSource?.Dispose (); + + _eventReady?.Dispose (); + + _resultQueue?.Clear (); + _waitForProbe?.Dispose (); + _netEvents?.Dispose (); + _netEvents = null; + + _mainLoop = null; + } + + private void NetInputHandler () + { + while (_mainLoop is { }) + { + try + { + if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested) + { + _waitForProbe.Wait (_inputHandlerTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return; + } + finally + { + if (_waitForProbe.IsSet) + { + _waitForProbe.Reset (); + } + } + + if (_inputHandlerTokenSource.IsCancellationRequested) + { + return; + } + + _inputHandlerTokenSource.Token.ThrowIfCancellationRequested (); + + if (_resultQueue.Count == 0) + { + _resultQueue.Enqueue (_netEvents.DequeueInput ()); + } + + try + { + while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out NetEvents.InputResult? result) && result is null) + { + // Dequeue null values + _resultQueue.TryDequeue (out _); + } + } + catch (InvalidOperationException) // Peek can raise an exception + { } + + if (_resultQueue.Count > 0) + { + _eventReady.Set (); + } + } + } +} diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs new file mode 100644 index 000000000..81a9f6b68 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs @@ -0,0 +1,125 @@ +using System.Runtime.InteropServices; + +namespace Terminal.Gui; + +internal class NetWinVTConsole +{ + private const uint DISABLE_NEWLINE_AUTO_RETURN = 8; + private const uint ENABLE_ECHO_INPUT = 4; + private const uint ENABLE_EXTENDED_FLAGS = 128; + private const uint ENABLE_INSERT_MODE = 32; + private const uint ENABLE_LINE_INPUT = 2; + private const uint ENABLE_LVB_GRID_WORLDWIDE = 10; + private const uint ENABLE_MOUSE_INPUT = 16; + + // Input modes. + private const uint ENABLE_PROCESSED_INPUT = 1; + + // Output modes. + private const uint ENABLE_PROCESSED_OUTPUT = 1; + private const uint ENABLE_QUICK_EDIT_MODE = 64; + private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; + private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; + private const uint ENABLE_WINDOW_INPUT = 8; + private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2; + private const int STD_ERROR_HANDLE = -12; + private const int STD_INPUT_HANDLE = -10; + private const int STD_OUTPUT_HANDLE = -11; + + private readonly nint _errorHandle; + private readonly nint _inputHandle; + private readonly uint _originalErrorConsoleMode; + private readonly uint _originalInputConsoleMode; + private readonly uint _originalOutputConsoleMode; + private readonly nint _outputHandle; + + public NetWinVTConsole () + { + _inputHandle = GetStdHandle (STD_INPUT_HANDLE); + + if (!GetConsoleMode (_inputHandle, out uint mode)) + { + throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}."); + } + + _originalInputConsoleMode = mode; + + if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) + { + mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + + if (!SetConsoleMode (_inputHandle, mode)) + { + throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}."); + } + } + + _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + + if (!GetConsoleMode (_outputHandle, out mode)) + { + throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}."); + } + + _originalOutputConsoleMode = mode; + + if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) + { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + + if (!SetConsoleMode (_outputHandle, mode)) + { + throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}."); + } + } + + _errorHandle = GetStdHandle (STD_ERROR_HANDLE); + + if (!GetConsoleMode (_errorHandle, out mode)) + { + throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}."); + } + + _originalErrorConsoleMode = mode; + + if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN) + { + mode |= DISABLE_NEWLINE_AUTO_RETURN; + + if (!SetConsoleMode (_errorHandle, mode)) + { + throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}."); + } + } + } + + public void Cleanup () + { + if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode)) + { + throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}."); + } + + if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode)) + { + throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}."); + } + + if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode)) + { + throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}."); + } + } + + [DllImport ("kernel32.dll")] + private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); + + [DllImport ("kernel32.dll")] + private static extern uint GetLastError (); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern nint GetStdHandle (int nStdHandle); + + [DllImport ("kernel32.dll")] + private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode); +} diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs new file mode 100644 index 000000000..22c0e1035 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -0,0 +1,1109 @@ +// TODO: #nullable enable +using System.ComponentModel; +using System.Runtime.InteropServices; +using Terminal.Gui.ConsoleDrivers; + +namespace Terminal.Gui; + +internal class WindowsConsole +{ + internal WindowsMainLoop _mainLoop; + + public const int STD_OUTPUT_HANDLE = -11; + public const int STD_INPUT_HANDLE = -10; + + private readonly nint _inputHandle; + private nint _outputHandle; + //private nint _screenBuffer; + private readonly uint _originalConsoleMode; + private CursorVisibility? _initialCursorVisibility; + private CursorVisibility? _currentCursorVisibility; + private CursorVisibility? _pendingCursorVisibility; + private readonly StringBuilder _stringBuilder = new (256 * 1024); + private string _lastWrite = string.Empty; + + public WindowsConsole () + { + _inputHandle = GetStdHandle (STD_INPUT_HANDLE); + _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + _originalConsoleMode = ConsoleMode; + uint newConsoleMode = _originalConsoleMode; + newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags); + newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; + newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; + ConsoleMode = newConsoleMode; + } + + private CharInfo [] _originalStdOutChars; + + public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) + { + //Debug.WriteLine ("WriteToConsole"); + + //if (_screenBuffer == nint.Zero) + //{ + // ReadFromConsoleOutput (size, bufferSize, ref window); + //} + + var result = false; + + if (force16Colors) + { + var i = 0; + CharInfo [] ci = new CharInfo [charInfoBuffer.Length]; + + foreach (ExtendedCharInfo info in charInfoBuffer) + { + ci [i++] = new CharInfo + { + Char = new CharUnion { UnicodeChar = info.Char }, + Attributes = + (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4)) + }; + } + + result = WriteConsoleOutput (_outputHandle, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); + } + else + { + _stringBuilder.Clear (); + + _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); + + Attribute? prev = null; + + foreach (ExtendedCharInfo info in charInfoBuffer) + { + Attribute attr = info.Attribute; + + if (attr != prev) + { + prev = attr; + _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); + _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); + } + + if (info.Char != '\x1b') + { + if (!info.Empty) + { + _stringBuilder.Append (info.Char); + } + } + else + { + _stringBuilder.Append (' '); + } + } + + _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_HideCursor); + + var s = _stringBuilder.ToString (); + + // TODO: requires extensive testing if we go down this route + // If console output has changed + if (s != _lastWrite) + { + // supply console with the new content + result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero); + } + + _lastWrite = s; + + foreach (var sixel in Application.Sixel) + { + SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y)); + WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + } + } + + if (!result) + { + int err = Marshal.GetLastWin32Error (); + + if (err != 0) + { + throw new Win32Exception (err); + } + } + + return result; + } + + internal bool WriteANSI (string ansi) + { + if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero)) + { + // Flush the output to make sure it's sent immediately + return FlushFileBuffers (_outputHandle); + } + + return false; + } + + public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) + { + //_screenBuffer = CreateConsoleScreenBuffer ( + // DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + // ShareMode.FileShareRead | ShareMode.FileShareWrite, + // nint.Zero, + // 1, + // nint.Zero + // ); + + //if (_screenBuffer == INVALID_HANDLE_VALUE) + //{ + // int err = Marshal.GetLastWin32Error (); + + // if (err != 0) + // { + // throw new Win32Exception (err); + // } + //} + + SetInitialCursorVisibility (); + + //if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + //{ + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + //} + + _originalStdOutChars = new CharInfo [size.Height * size.Width]; + + if (!ReadConsoleOutput (_outputHandle, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + } + + public bool SetCursorPosition (Coord position) + { + return SetConsoleCursorPosition (_outputHandle, position); + } + + public void SetInitialCursorVisibility () + { + if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility)) + { + _initialCursorVisibility = visibility; + } + } + + public bool GetCursorVisibility (out CursorVisibility visibility) + { + if (_outputHandle == nint.Zero) + { + visibility = CursorVisibility.Invisible; + + return false; + } + + if (!GetConsoleCursorInfo (_outputHandle, out ConsoleCursorInfo info)) + { + int err = Marshal.GetLastWin32Error (); + + if (err != 0) + { + throw new Win32Exception (err); + } + + visibility = CursorVisibility.Default; + + return false; + } + + if (!info.bVisible) + { + visibility = CursorVisibility.Invisible; + } + else if (info.dwSize > 50) + { + visibility = CursorVisibility.Default; + } + else + { + visibility = CursorVisibility.Default; + } + + return true; + } + + public bool EnsureCursorVisibility () + { + if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value)) + { + _pendingCursorVisibility = null; + + return true; + } + + return false; + } + + public void ForceRefreshCursorVisibility () + { + if (_currentCursorVisibility.HasValue) + { + _pendingCursorVisibility = _currentCursorVisibility; + _currentCursorVisibility = null; + } + } + + public bool SetCursorVisibility (CursorVisibility visibility) + { + if (_initialCursorVisibility.HasValue == false) + { + _pendingCursorVisibility = visibility; + + return false; + } + + if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility) + { + var info = new ConsoleCursorInfo + { + dwSize = (uint)visibility & 0x00FF, + bVisible = ((uint)visibility & 0xFF00) != 0 + }; + + if (!SetConsoleCursorInfo (_outputHandle, ref info)) + { + return false; + } + + _currentCursorVisibility = visibility; + } + + return true; + } + + public void Cleanup () + { + if (_initialCursorVisibility.HasValue) + { + SetCursorVisibility (_initialCursorVisibility.Value); + } + + //SetConsoleOutputWindow (out _); + + ConsoleMode = _originalConsoleMode; + + _outputHandle = CreateConsoleScreenBuffer ( + DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + ShareMode.FileShareRead | ShareMode.FileShareWrite, + nint.Zero, + 1, + nint.Zero + ); + + if (!SetConsoleActiveScreenBuffer (_outputHandle)) + { + int err = Marshal.GetLastWin32Error (); + Console.WriteLine ("Error: {0}", err); + } + + //if (_screenBuffer != nint.Zero) + //{ + // CloseHandle (_screenBuffer); + //} + + //_screenBuffer = nint.Zero; + } + + //internal Size GetConsoleBufferWindow (out Point position) + //{ + // if (_screenBuffer == nint.Zero) + // { + // position = Point.Empty; + + // return Size.Empty; + // } + + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + // position = Point.Empty; + + // return Size.Empty; + // } + + // Size sz = new ( + // csbi.srWindow.Right - csbi.srWindow.Left + 1, + // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + // position = new (csbi.srWindow.Left, csbi.srWindow.Top); + + // return sz; + //} + + internal Size GetConsoleOutputWindow (out Point position) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + Size sz = new ( + csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new (csbi.srWindow.Left, csbi.srWindow.Top); + + return sz; + } + + //internal Size SetConsoleWindow (short cols, short rows) + //{ + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + + // Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer); + // short newCols = Math.Min (cols, maxWinSize.X); + // short newRows = Math.Min (rows, maxWinSize.Y); + // csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); + // csbi.srWindow = new SmallRect (0, 0, newCols, newRows); + // csbi.dwMaximumWindowSize = new Coord (newCols, newRows); + + // if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + + // var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); + + // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + // { + // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + // return new (cols, rows); + // } + + // SetConsoleOutputWindow (csbi); + + // return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); + //} + + //private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) + //{ + // if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + //} + + //internal Size SetConsoleOutputWindow (out Point position) + //{ + // if (_screenBuffer == nint.Zero) + // { + // position = Point.Empty; + + // return Size.Empty; + // } + + // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + // csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + + // Size sz = new ( + // csbi.srWindow.Right - csbi.srWindow.Left + 1, + // Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); + // position = new (csbi.srWindow.Left, csbi.srWindow.Top); + // SetConsoleOutputWindow (csbi); + // var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); + + // if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + + // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + // { + // throw new Win32Exception (Marshal.GetLastWin32Error ()); + // } + + // return sz; + //} + + private uint ConsoleMode + { + get + { + GetConsoleMode (_inputHandle, out uint v); + + return v; + } + set => SetConsoleMode (_inputHandle, value); + } + + [Flags] + public enum ConsoleModes : uint + { + EnableProcessedInput = 1, + EnableMouseInput = 16, + EnableQuickEditMode = 64, + EnableExtendedFlags = 128 + } + + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct KeyEventRecord + { + [FieldOffset (0)] + [MarshalAs (UnmanagedType.Bool)] + public bool bKeyDown; + + [FieldOffset (4)] + [MarshalAs (UnmanagedType.U2)] + public ushort wRepeatCount; + + [FieldOffset (6)] + [MarshalAs (UnmanagedType.U2)] + public ConsoleKeyMapping.VK wVirtualKeyCode; + + [FieldOffset (8)] + [MarshalAs (UnmanagedType.U2)] + public ushort wVirtualScanCode; + + [FieldOffset (10)] + public char UnicodeChar; + + [FieldOffset (12)] + [MarshalAs (UnmanagedType.U4)] + public ControlKeyState dwControlKeyState; + + public readonly override string ToString () + { + return + $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]"; + } + } + + [Flags] + public enum ButtonState + { + NoButtonPressed = 0, + Button1Pressed = 1, + Button2Pressed = 4, + Button3Pressed = 8, + Button4Pressed = 16, + RightmostButtonPressed = 2 + } + + [Flags] + public enum ControlKeyState + { + NoControlKeyPressed = 0, + RightAltPressed = 1, + LeftAltPressed = 2, + RightControlPressed = 4, + LeftControlPressed = 8, + ShiftPressed = 16, + NumlockOn = 32, + ScrolllockOn = 64, + CapslockOn = 128, + EnhancedKey = 256 + } + + [Flags] + public enum EventFlags + { + NoEvent = 0, + MouseMoved = 1, + DoubleClick = 2, + MouseWheeled = 4, + MouseHorizontalWheeled = 8 + } + + [StructLayout (LayoutKind.Explicit)] + public struct MouseEventRecord + { + [FieldOffset (0)] + public Coord MousePosition; + + [FieldOffset (4)] + public ButtonState ButtonState; + + [FieldOffset (8)] + public ControlKeyState ControlKeyState; + + [FieldOffset (12)] + public EventFlags EventFlags; + + public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; } + } + + public struct WindowBufferSizeRecord + { + public Coord _size; + + public WindowBufferSizeRecord (short x, short y) { _size = new Coord (x, y); } + + public readonly override string ToString () { return $"[WindowBufferSize{_size}"; } + } + + [StructLayout (LayoutKind.Sequential)] + public struct MenuEventRecord + { + public uint dwCommandId; + } + + [StructLayout (LayoutKind.Sequential)] + public struct FocusEventRecord + { + public uint bSetFocus; + } + + public enum EventType : ushort + { + Focus = 0x10, + Key = 0x1, + Menu = 0x8, + Mouse = 2, + WindowBufferSize = 4 + } + + [StructLayout (LayoutKind.Explicit)] + public struct InputRecord + { + [FieldOffset (0)] + public EventType EventType; + + [FieldOffset (4)] + public KeyEventRecord KeyEvent; + + [FieldOffset (4)] + public MouseEventRecord MouseEvent; + + [FieldOffset (4)] + public WindowBufferSizeRecord WindowBufferSizeEvent; + + [FieldOffset (4)] + public MenuEventRecord MenuEvent; + + [FieldOffset (4)] + public FocusEventRecord FocusEvent; + + public readonly override string ToString () + { + return EventType switch + { + EventType.Focus => FocusEvent.ToString (), + EventType.Key => KeyEvent.ToString (), + EventType.Menu => MenuEvent.ToString (), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + }; + } + } + + [Flags] + private enum ShareMode : uint + { + FileShareRead = 1, + FileShareWrite = 2 + } + + [Flags] + private enum DesiredAccess : uint + { + GenericRead = 2147483648, + GenericWrite = 1073741824 + } + + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleScreenBufferInfo + { + public Coord dwSize; + public Coord dwCursorPosition; + public ushort wAttributes; + public SmallRect srWindow; + public Coord dwMaximumWindowSize; + } + + [StructLayout (LayoutKind.Sequential)] + public struct Coord + { + public short X; + public short Y; + + public Coord (short x, short y) + { + X = x; + Y = y; + } + + public readonly override string ToString () { return $"({X},{Y})"; } + } + + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct CharUnion + { + [FieldOffset (0)] + public char UnicodeChar; + + [FieldOffset (0)] + public byte AsciiChar; + } + + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct CharInfo + { + [FieldOffset (0)] + public CharUnion Char; + + [FieldOffset (2)] + public ushort Attributes; + } + + public struct ExtendedCharInfo + { + public char Char { get; set; } + public Attribute Attribute { get; set; } + public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences + + public ExtendedCharInfo (char character, Attribute attribute) + { + Char = character; + Attribute = attribute; + Empty = false; + } + } + + [StructLayout (LayoutKind.Sequential)] + public struct SmallRect + { + public short Left; + public short Top; + public short Right; + public short Bottom; + + public SmallRect (short left, short top, short right, short bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; } + + public static void Update (ref SmallRect rect, short col, short row) + { + if (rect.Left == -1) + { + rect.Left = rect.Right = col; + rect.Bottom = rect.Top = row; + + return; + } + + if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom) + { + return; + } + + if (col < rect.Left) + { + rect.Left = col; + } + + if (col > rect.Right) + { + rect.Right = col; + } + + if (row < rect.Top) + { + rect.Top = row; + } + + if (row > rect.Bottom) + { + rect.Bottom = row; + } + } + + public readonly override string ToString () { return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; } + } + + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleKeyInfoEx + { + public ConsoleKeyInfo ConsoleKeyInfo; + public bool CapsLock; + public bool NumLock; + public bool ScrollLock; + + public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock) + { + ConsoleKeyInfo = consoleKeyInfo; + CapsLock = capslock; + NumLock = numlock; + ScrollLock = scrolllock; + } + + /// + /// Prints a ConsoleKeyInfoEx structure + /// + /// + /// + public readonly string ToString (ConsoleKeyInfoEx ex) + { + var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar); + var sb = new StringBuilder (); + sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})"); + sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); + sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); + sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); + sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) "); + sb.Append (ex.CapsLock ? "caps," : string.Empty); + sb.Append (ex.NumLock ? "num," : string.Empty); + sb.Append (ex.ScrollLock ? "scroll," : string.Empty); + string s = sb.ToString ().TrimEnd (',').TrimEnd (' '); + + return $"[ConsoleKeyInfoEx({s})]"; + } + } + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern nint GetStdHandle (int nStdHandle); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle (nint handle); + + [DllImport ("kernel32.dll", SetLastError = true)] + public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead); + + [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] + public static extern bool ReadConsoleInput ( + nint hConsoleInput, + out InputRecord lpBuffer, + uint nLength, + out uint lpNumberOfEventsRead + ); + + [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool ReadConsoleOutput ( + nint hConsoleOutput, + [Out] CharInfo [] lpBuffer, + Coord dwBufferSize, + Coord dwBufferCoord, + ref SmallRect lpReadRegion + ); + + // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput + [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool WriteConsoleOutput ( + nint hConsoleOutput, + CharInfo [] lpBuffer, + Coord dwBufferSize, + Coord dwBufferCoord, + ref SmallRect lpWriteRegion + ); + + [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool WriteConsole ( + nint hConsoleOutput, + string lpbufer, + uint NumberOfCharsToWriten, + out uint lpNumberOfCharsWritten, + nint lpReserved + ); + + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool FlushFileBuffers (nint hFile); + + [DllImport ("kernel32.dll")] + private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition); + + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleCursorInfo + { + /// + /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100. + /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal + /// line at the bottom of the cell. + /// + public uint dwSize; + public bool bVisible; + } + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool GetConsoleCursorInfo (nint hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo); + + [DllImport ("kernel32.dll")] + private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); + + [DllImport ("kernel32.dll")] + private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern nint CreateConsoleScreenBuffer ( + DesiredAccess dwDesiredAccess, + ShareMode dwShareMode, + nint secutiryAttributes, + uint flags, + nint screenBufferData + ); + + internal static nint INVALID_HANDLE_VALUE = new (-1); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleActiveScreenBuffer (nint Handle); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents); + + internal uint GetNumberOfConsoleInputEvents () + { + if (!GetNumberOfConsoleInputEvents (_inputHandle, out uint numOfEvents)) + { + Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}"); + + return 0; + } + + return numOfEvents; + } + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool FlushConsoleInputBuffer (nint handle); + + internal void FlushConsoleInputBuffer () + { + if (!FlushConsoleInputBuffer (_inputHandle)) + { + Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}"); + } + } + + private int _retries; + + public InputRecord [] ReadConsoleInput () + { + const int bufferSize = 1; + InputRecord inputRecord = default; + uint numberEventsRead = 0; + StringBuilder ansiSequence = new StringBuilder (); + bool readingSequence = false; + bool raisedResponse = false; + + while (true) + { + try + { + // Peek to check if there is any input available + if (PeekConsoleInput (_inputHandle, out _, bufferSize, out uint eventsRead) && eventsRead > 0) + { + // Read the input since it is available + ReadConsoleInput ( + _inputHandle, + out inputRecord, + bufferSize, + out numberEventsRead); + + if (inputRecord.EventType == EventType.Key) + { + KeyEventRecord keyEvent = inputRecord.KeyEvent; + + if (keyEvent.bKeyDown) + { + char inputChar = keyEvent.UnicodeChar; + + // Check if input is part of an ANSI escape sequence + if (inputChar == '\u001B') // Escape character + { + // Peek to check if there is any input available with key event and bKeyDown + if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, bufferSize, out eventsRead) && eventsRead > 0) + { + if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) + { + // It's really an ANSI request response + readingSequence = true; + ansiSequence.Clear (); // Start a new sequence + ansiSequence.Append (inputChar); + + continue; + } + } + } + else if (readingSequence) + { + ansiSequence.Append (inputChar); + + // Check if the sequence has ended with an expected command terminator + if (_mainLoop.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out EscSeqReqStatus seqReqStatus)) + { + // Finished reading the sequence and remove the enqueued request + _mainLoop.EscSeqRequests.Remove (seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + raisedResponse = true; + seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); + } + } + + continue; + } + } + } + } + + if (readingSequence && !raisedResponse && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); + } + + _retries = 0; + } + else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + { + if (_retries > 1) + { + if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + { + lock (seqReqStatus!.AnsiRequest._responseLock) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.Response = string.Empty; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + } + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + + return numberEventsRead == 0 + ? null + : [inputRecord]; + } + catch (Exception) + { + return null; + } + } + } + +#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. + [DllImport ("kernel32.dll", ExactSpelling = true)] + static extern IntPtr GetConsoleWindow (); + + [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool ShowWindow (IntPtr hWnd, int nCmdShow); + + public const int HIDE = 0; + public const int MAXIMIZE = 3; + public const int MINIMIZE = 6; + public const int RESTORE = 9; + + internal void ShowWindow (int state) + { + IntPtr thisConsole = GetConsoleWindow (); + ShowWindow (thisConsole, state); + } +#endif + + // See: https://github.com/gui-cs/Terminal.Gui/issues/357 + + [StructLayout (LayoutKind.Sequential)] + public struct CONSOLE_SCREEN_BUFFER_INFOEX + { + public uint cbSize; + public Coord dwSize; + public Coord dwCursorPosition; + public ushort wAttributes; + public SmallRect srWindow; + public Coord dwMaximumWindowSize; + public ushort wPopupAttributes; + public bool bFullscreenSupported; + + [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)] + public COLORREF [] ColorTable; + } + + [StructLayout (LayoutKind.Explicit, Size = 4)] + public struct COLORREF + { + public COLORREF (byte r, byte g, byte b) + { + Value = 0; + R = r; + G = g; + B = b; + } + + public COLORREF (uint value) + { + R = 0; + G = 0; + B = 0; + Value = value & 0x00FFFFFF; + } + + [FieldOffset (0)] + public byte R; + + [FieldOffset (1)] + public byte G; + + [FieldOffset (2)] + public byte B; + + [FieldOffset (0)] + public uint Value; + } + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleWindowInfo ( + nint hConsoleOutput, + bool bAbsolute, + [In] ref SmallRect lpConsoleWindow + ); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern Coord GetLargestConsoleWindowSize ( + nint hConsoleOutput + ); +} diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs similarity index 60% rename from Terminal.Gui/ConsoleDrivers/WindowsDriver.cs rename to Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 83763e081..6f20539d8 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -1,4 +1,5 @@ -// +// TODO: #nullable enable +// // WindowsDriver.cs: Windows specific driver // @@ -23,1109 +24,6 @@ using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; namespace Terminal.Gui; -internal class WindowsConsole -{ - internal WindowsMainLoop _mainLoop; - - public const int STD_OUTPUT_HANDLE = -11; - public const int STD_INPUT_HANDLE = -10; - - private readonly nint _inputHandle; - private nint _outputHandle; - //private nint _screenBuffer; - private readonly uint _originalConsoleMode; - private CursorVisibility? _initialCursorVisibility; - private CursorVisibility? _currentCursorVisibility; - private CursorVisibility? _pendingCursorVisibility; - private readonly StringBuilder _stringBuilder = new (256 * 1024); - private string _lastWrite = string.Empty; - - public WindowsConsole () - { - _inputHandle = GetStdHandle (STD_INPUT_HANDLE); - _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); - _originalConsoleMode = ConsoleMode; - uint newConsoleMode = _originalConsoleMode; - newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags); - newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; - newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; - ConsoleMode = newConsoleMode; - } - - private CharInfo [] _originalStdOutChars; - - public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) - { - //Debug.WriteLine ("WriteToConsole"); - - //if (_screenBuffer == nint.Zero) - //{ - // ReadFromConsoleOutput (size, bufferSize, ref window); - //} - - var result = false; - - if (force16Colors) - { - var i = 0; - CharInfo [] ci = new CharInfo [charInfoBuffer.Length]; - - foreach (ExtendedCharInfo info in charInfoBuffer) - { - ci [i++] = new CharInfo - { - Char = new CharUnion { UnicodeChar = info.Char }, - Attributes = - (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4)) - }; - } - - result = WriteConsoleOutput (_outputHandle, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); - } - else - { - _stringBuilder.Clear (); - - _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); - - Attribute? prev = null; - - foreach (ExtendedCharInfo info in charInfoBuffer) - { - Attribute attr = info.Attribute; - - if (attr != prev) - { - prev = attr; - _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); - _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); - } - - if (info.Char != '\x1b') - { - if (!info.Empty) - { - _stringBuilder.Append (info.Char); - } - } - else - { - _stringBuilder.Append (' '); - } - } - - _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); - _stringBuilder.Append (EscSeqUtils.CSI_HideCursor); - - var s = _stringBuilder.ToString (); - - // TODO: requires extensive testing if we go down this route - // If console output has changed - if (s != _lastWrite) - { - // supply console with the new content - result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero); - } - - _lastWrite = s; - - foreach (var sixel in Application.Sixel) - { - SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y)); - WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); - } - } - - if (!result) - { - int err = Marshal.GetLastWin32Error (); - - if (err != 0) - { - throw new Win32Exception (err); - } - } - - return result; - } - - internal bool WriteANSI (string ansi) - { - if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero)) - { - // Flush the output to make sure it's sent immediately - return FlushFileBuffers (_outputHandle); - } - - return false; - } - - public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) - { - //_screenBuffer = CreateConsoleScreenBuffer ( - // DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - // ShareMode.FileShareRead | ShareMode.FileShareWrite, - // nint.Zero, - // 1, - // nint.Zero - // ); - - //if (_screenBuffer == INVALID_HANDLE_VALUE) - //{ - // int err = Marshal.GetLastWin32Error (); - - // if (err != 0) - // { - // throw new Win32Exception (err); - // } - //} - - SetInitialCursorVisibility (); - - //if (!SetConsoleActiveScreenBuffer (_screenBuffer)) - //{ - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - //} - - _originalStdOutChars = new CharInfo [size.Height * size.Width]; - - if (!ReadConsoleOutput (_outputHandle, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } - } - - public bool SetCursorPosition (Coord position) - { - return SetConsoleCursorPosition (_outputHandle, position); - } - - public void SetInitialCursorVisibility () - { - if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility)) - { - _initialCursorVisibility = visibility; - } - } - - public bool GetCursorVisibility (out CursorVisibility visibility) - { - if (_outputHandle == nint.Zero) - { - visibility = CursorVisibility.Invisible; - - return false; - } - - if (!GetConsoleCursorInfo (_outputHandle, out ConsoleCursorInfo info)) - { - int err = Marshal.GetLastWin32Error (); - - if (err != 0) - { - throw new Win32Exception (err); - } - - visibility = CursorVisibility.Default; - - return false; - } - - if (!info.bVisible) - { - visibility = CursorVisibility.Invisible; - } - else if (info.dwSize > 50) - { - visibility = CursorVisibility.Default; - } - else - { - visibility = CursorVisibility.Default; - } - - return true; - } - - public bool EnsureCursorVisibility () - { - if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value)) - { - _pendingCursorVisibility = null; - - return true; - } - - return false; - } - - public void ForceRefreshCursorVisibility () - { - if (_currentCursorVisibility.HasValue) - { - _pendingCursorVisibility = _currentCursorVisibility; - _currentCursorVisibility = null; - } - } - - public bool SetCursorVisibility (CursorVisibility visibility) - { - if (_initialCursorVisibility.HasValue == false) - { - _pendingCursorVisibility = visibility; - - return false; - } - - if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility) - { - var info = new ConsoleCursorInfo - { - dwSize = (uint)visibility & 0x00FF, - bVisible = ((uint)visibility & 0xFF00) != 0 - }; - - if (!SetConsoleCursorInfo (_outputHandle, ref info)) - { - return false; - } - - _currentCursorVisibility = visibility; - } - - return true; - } - - public void Cleanup () - { - if (_initialCursorVisibility.HasValue) - { - SetCursorVisibility (_initialCursorVisibility.Value); - } - - //SetConsoleOutputWindow (out _); - - ConsoleMode = _originalConsoleMode; - - _outputHandle = CreateConsoleScreenBuffer ( - DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - ShareMode.FileShareRead | ShareMode.FileShareWrite, - nint.Zero, - 1, - nint.Zero - ); - - if (!SetConsoleActiveScreenBuffer (_outputHandle)) - { - int err = Marshal.GetLastWin32Error (); - Console.WriteLine ("Error: {0}", err); - } - - //if (_screenBuffer != nint.Zero) - //{ - // CloseHandle (_screenBuffer); - //} - - //_screenBuffer = nint.Zero; - } - - //internal Size GetConsoleBufferWindow (out Point position) - //{ - // if (_screenBuffer == nint.Zero) - // { - // position = Point.Empty; - - // return Size.Empty; - // } - - // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - - // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - // position = Point.Empty; - - // return Size.Empty; - // } - - // Size sz = new ( - // csbi.srWindow.Right - csbi.srWindow.Left + 1, - // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - // position = new (csbi.srWindow.Left, csbi.srWindow.Top); - - // return sz; - //} - - internal Size GetConsoleOutputWindow (out Point position) - { - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); - - if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } - - Size sz = new ( - csbi.srWindow.Right - csbi.srWindow.Left + 1, - csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - position = new (csbi.srWindow.Left, csbi.srWindow.Top); - - return sz; - } - - //internal Size SetConsoleWindow (short cols, short rows) - //{ - // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - - // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer); - // short newCols = Math.Min (cols, maxWinSize.X); - // short newRows = Math.Min (rows, maxWinSize.Y); - // csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); - // csbi.srWindow = new SmallRect (0, 0, newCols, newRows); - // csbi.dwMaximumWindowSize = new Coord (newCols, newRows); - - // if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); - - // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) - // { - // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - // return new (cols, rows); - // } - - // SetConsoleOutputWindow (csbi); - - // return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); - //} - - //private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) - //{ - // if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - //} - - //internal Size SetConsoleOutputWindow (out Point position) - //{ - // if (_screenBuffer == nint.Zero) - // { - // position = Point.Empty; - - // return Size.Empty; - // } - - // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - // csbi.cbSize = (uint)Marshal.SizeOf (csbi); - - // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // Size sz = new ( - // csbi.srWindow.Right - csbi.srWindow.Left + 1, - // Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); - // position = new (csbi.srWindow.Left, csbi.srWindow.Top); - // SetConsoleOutputWindow (csbi); - // var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); - - // if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // return sz; - //} - - private uint ConsoleMode - { - get - { - GetConsoleMode (_inputHandle, out uint v); - - return v; - } - set => SetConsoleMode (_inputHandle, value); - } - - [Flags] - public enum ConsoleModes : uint - { - EnableProcessedInput = 1, - EnableMouseInput = 16, - EnableQuickEditMode = 64, - EnableExtendedFlags = 128 - } - - [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct KeyEventRecord - { - [FieldOffset (0)] - [MarshalAs (UnmanagedType.Bool)] - public bool bKeyDown; - - [FieldOffset (4)] - [MarshalAs (UnmanagedType.U2)] - public ushort wRepeatCount; - - [FieldOffset (6)] - [MarshalAs (UnmanagedType.U2)] - public VK wVirtualKeyCode; - - [FieldOffset (8)] - [MarshalAs (UnmanagedType.U2)] - public ushort wVirtualScanCode; - - [FieldOffset (10)] - public char UnicodeChar; - - [FieldOffset (12)] - [MarshalAs (UnmanagedType.U4)] - public ControlKeyState dwControlKeyState; - - public readonly override string ToString () - { - return - $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]"; - } - } - - [Flags] - public enum ButtonState - { - NoButtonPressed = 0, - Button1Pressed = 1, - Button2Pressed = 4, - Button3Pressed = 8, - Button4Pressed = 16, - RightmostButtonPressed = 2 - } - - [Flags] - public enum ControlKeyState - { - NoControlKeyPressed = 0, - RightAltPressed = 1, - LeftAltPressed = 2, - RightControlPressed = 4, - LeftControlPressed = 8, - ShiftPressed = 16, - NumlockOn = 32, - ScrolllockOn = 64, - CapslockOn = 128, - EnhancedKey = 256 - } - - [Flags] - public enum EventFlags - { - NoEvent = 0, - MouseMoved = 1, - DoubleClick = 2, - MouseWheeled = 4, - MouseHorizontalWheeled = 8 - } - - [StructLayout (LayoutKind.Explicit)] - public struct MouseEventRecord - { - [FieldOffset (0)] - public Coord MousePosition; - - [FieldOffset (4)] - public ButtonState ButtonState; - - [FieldOffset (8)] - public ControlKeyState ControlKeyState; - - [FieldOffset (12)] - public EventFlags EventFlags; - - public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; } - } - - public struct WindowBufferSizeRecord - { - public Coord _size; - - public WindowBufferSizeRecord (short x, short y) { _size = new Coord (x, y); } - - public readonly override string ToString () { return $"[WindowBufferSize{_size}"; } - } - - [StructLayout (LayoutKind.Sequential)] - public struct MenuEventRecord - { - public uint dwCommandId; - } - - [StructLayout (LayoutKind.Sequential)] - public struct FocusEventRecord - { - public uint bSetFocus; - } - - public enum EventType : ushort - { - Focus = 0x10, - Key = 0x1, - Menu = 0x8, - Mouse = 2, - WindowBufferSize = 4 - } - - [StructLayout (LayoutKind.Explicit)] - public struct InputRecord - { - [FieldOffset (0)] - public EventType EventType; - - [FieldOffset (4)] - public KeyEventRecord KeyEvent; - - [FieldOffset (4)] - public MouseEventRecord MouseEvent; - - [FieldOffset (4)] - public WindowBufferSizeRecord WindowBufferSizeEvent; - - [FieldOffset (4)] - public MenuEventRecord MenuEvent; - - [FieldOffset (4)] - public FocusEventRecord FocusEvent; - - public readonly override string ToString () - { - return EventType switch - { - EventType.Focus => FocusEvent.ToString (), - EventType.Key => KeyEvent.ToString (), - EventType.Menu => MenuEvent.ToString (), - EventType.Mouse => MouseEvent.ToString (), - EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), - _ => "Unknown event type: " + EventType - }; - } - } - - [Flags] - private enum ShareMode : uint - { - FileShareRead = 1, - FileShareWrite = 2 - } - - [Flags] - private enum DesiredAccess : uint - { - GenericRead = 2147483648, - GenericWrite = 1073741824 - } - - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleScreenBufferInfo - { - public Coord dwSize; - public Coord dwCursorPosition; - public ushort wAttributes; - public SmallRect srWindow; - public Coord dwMaximumWindowSize; - } - - [StructLayout (LayoutKind.Sequential)] - public struct Coord - { - public short X; - public short Y; - - public Coord (short x, short y) - { - X = x; - Y = y; - } - - public readonly override string ToString () { return $"({X},{Y})"; } - } - - [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct CharUnion - { - [FieldOffset (0)] - public char UnicodeChar; - - [FieldOffset (0)] - public byte AsciiChar; - } - - [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct CharInfo - { - [FieldOffset (0)] - public CharUnion Char; - - [FieldOffset (2)] - public ushort Attributes; - } - - public struct ExtendedCharInfo - { - public char Char { get; set; } - public Attribute Attribute { get; set; } - public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences - - public ExtendedCharInfo (char character, Attribute attribute) - { - Char = character; - Attribute = attribute; - Empty = false; - } - } - - [StructLayout (LayoutKind.Sequential)] - public struct SmallRect - { - public short Left; - public short Top; - public short Right; - public short Bottom; - - public SmallRect (short left, short top, short right, short bottom) - { - Left = left; - Top = top; - Right = right; - Bottom = bottom; - } - - public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; } - - public static void Update (ref SmallRect rect, short col, short row) - { - if (rect.Left == -1) - { - rect.Left = rect.Right = col; - rect.Bottom = rect.Top = row; - - return; - } - - if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom) - { - return; - } - - if (col < rect.Left) - { - rect.Left = col; - } - - if (col > rect.Right) - { - rect.Right = col; - } - - if (row < rect.Top) - { - rect.Top = row; - } - - if (row > rect.Bottom) - { - rect.Bottom = row; - } - } - - public readonly override string ToString () { return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; } - } - - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleKeyInfoEx - { - public ConsoleKeyInfo ConsoleKeyInfo; - public bool CapsLock; - public bool NumLock; - public bool ScrollLock; - - public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock) - { - ConsoleKeyInfo = consoleKeyInfo; - CapsLock = capslock; - NumLock = numlock; - ScrollLock = scrolllock; - } - - /// - /// Prints a ConsoleKeyInfoEx structure - /// - /// - /// - public readonly string ToString (ConsoleKeyInfoEx ex) - { - var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar); - var sb = new StringBuilder (); - sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})"); - sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); - sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); - sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); - sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) "); - sb.Append (ex.CapsLock ? "caps," : string.Empty); - sb.Append (ex.NumLock ? "num," : string.Empty); - sb.Append (ex.ScrollLock ? "scroll," : string.Empty); - string s = sb.ToString ().TrimEnd (',').TrimEnd (' '); - - return $"[ConsoleKeyInfoEx({s})]"; - } - } - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern nint GetStdHandle (int nStdHandle); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle (nint handle); - - [DllImport ("kernel32.dll", SetLastError = true)] - public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead); - - [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] - public static extern bool ReadConsoleInput ( - nint hConsoleInput, - out InputRecord lpBuffer, - uint nLength, - out uint lpNumberOfEventsRead - ); - - [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool ReadConsoleOutput ( - nint hConsoleOutput, - [Out] CharInfo [] lpBuffer, - Coord dwBufferSize, - Coord dwBufferCoord, - ref SmallRect lpReadRegion - ); - - // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput - [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool WriteConsoleOutput ( - nint hConsoleOutput, - CharInfo [] lpBuffer, - Coord dwBufferSize, - Coord dwBufferCoord, - ref SmallRect lpWriteRegion - ); - - [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool WriteConsole ( - nint hConsoleOutput, - string lpbufer, - uint NumberOfCharsToWriten, - out uint lpNumberOfCharsWritten, - nint lpReserved - ); - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool FlushFileBuffers (nint hFile); - - [DllImport ("kernel32.dll")] - private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition); - - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleCursorInfo - { - /// - /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100. - /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal - /// line at the bottom of the cell. - /// - public uint dwSize; - public bool bVisible; - } - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool GetConsoleCursorInfo (nint hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo); - - [DllImport ("kernel32.dll")] - private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); - - [DllImport ("kernel32.dll")] - private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern nint CreateConsoleScreenBuffer ( - DesiredAccess dwDesiredAccess, - ShareMode dwShareMode, - nint secutiryAttributes, - uint flags, - nint screenBufferData - ); - - internal static nint INVALID_HANDLE_VALUE = new (-1); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleActiveScreenBuffer (nint Handle); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents); - - internal uint GetNumberOfConsoleInputEvents () - { - if (!GetNumberOfConsoleInputEvents (_inputHandle, out uint numOfEvents)) - { - Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}"); - - return 0; - } - - return numOfEvents; - } - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool FlushConsoleInputBuffer (nint handle); - - internal void FlushConsoleInputBuffer () - { - if (!FlushConsoleInputBuffer (_inputHandle)) - { - Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}"); - } - } - - private int _retries; - - public InputRecord [] ReadConsoleInput () - { - const int bufferSize = 1; - InputRecord inputRecord = default; - uint numberEventsRead = 0; - StringBuilder ansiSequence = new StringBuilder (); - bool readingSequence = false; - bool raisedResponse = false; - - while (true) - { - try - { - // Peek to check if there is any input available - if (PeekConsoleInput (_inputHandle, out _, bufferSize, out uint eventsRead) && eventsRead > 0) - { - // Read the input since it is available - ReadConsoleInput ( - _inputHandle, - out inputRecord, - bufferSize, - out numberEventsRead); - - if (inputRecord.EventType == EventType.Key) - { - KeyEventRecord keyEvent = inputRecord.KeyEvent; - - if (keyEvent.bKeyDown) - { - char inputChar = keyEvent.UnicodeChar; - - // Check if input is part of an ANSI escape sequence - if (inputChar == '\u001B') // Escape character - { - // Peek to check if there is any input available with key event and bKeyDown - if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, bufferSize, out eventsRead) && eventsRead > 0) - { - if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) - { - // It's really an ANSI request response - readingSequence = true; - ansiSequence.Clear (); // Start a new sequence - ansiSequence.Append (inputChar); - - continue; - } - } - } - else if (readingSequence) - { - ansiSequence.Append (inputChar); - - // Check if the sequence has ended with an expected command terminator - if (_mainLoop.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out EscSeqReqStatus seqReqStatus)) - { - // Finished reading the sequence and remove the enqueued request - _mainLoop.EscSeqRequests.Remove (seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) - { - raisedResponse = true; - seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); - } - } - - continue; - } - } - } - } - - if (readingSequence && !raisedResponse && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) - { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) - { - seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); - } - - _retries = 0; - } - else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) - { - if (_retries > 1) - { - if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) - { - lock (seqReqStatus!.AnsiRequest._responseLock) - { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.Response = string.Empty; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } - - return numberEventsRead == 0 - ? null - : [inputRecord]; - } - catch (Exception) - { - return null; - } - } - } - -#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. - [DllImport ("kernel32.dll", ExactSpelling = true)] - static extern IntPtr GetConsoleWindow (); - - [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - static extern bool ShowWindow (IntPtr hWnd, int nCmdShow); - - public const int HIDE = 0; - public const int MAXIMIZE = 3; - public const int MINIMIZE = 6; - public const int RESTORE = 9; - - internal void ShowWindow (int state) - { - IntPtr thisConsole = GetConsoleWindow (); - ShowWindow (thisConsole, state); - } -#endif - - // See: https://github.com/gui-cs/Terminal.Gui/issues/357 - - [StructLayout (LayoutKind.Sequential)] - public struct CONSOLE_SCREEN_BUFFER_INFOEX - { - public uint cbSize; - public Coord dwSize; - public Coord dwCursorPosition; - public ushort wAttributes; - public SmallRect srWindow; - public Coord dwMaximumWindowSize; - public ushort wPopupAttributes; - public bool bFullscreenSupported; - - [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)] - public COLORREF [] ColorTable; - } - - [StructLayout (LayoutKind.Explicit, Size = 4)] - public struct COLORREF - { - public COLORREF (byte r, byte g, byte b) - { - Value = 0; - R = r; - G = g; - B = b; - } - - public COLORREF (uint value) - { - R = 0; - G = 0; - B = 0; - Value = value & 0x00FFFFFF; - } - - [FieldOffset (0)] - public byte R; - - [FieldOffset (1)] - public byte G; - - [FieldOffset (2)] - public byte B; - - [FieldOffset (0)] - public uint Value; - } - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleWindowInfo ( - nint hConsoleOutput, - bool bAbsolute, - [In] ref SmallRect lpConsoleWindow - ); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern Coord GetLargestConsoleWindowSize ( - nint hConsoleOutput - ); -} - internal class WindowsDriver : ConsoleDriver { private readonly bool _isWindowsTerminal; diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 88d57c26b..abf668f25 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -164,8 +164,7 @@ - + diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 321af7c2e..f88c80820 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -12,14 +11,13 @@ public sealed class AnsiEscapeSequenceRequests : Scenario { private GraphView _graphView; - private DateTime start = DateTime.Now; private ScatterSeries _sentSeries; private ScatterSeries _answeredSeries; - private List sends = new (); + private readonly List _sends = new (); - private object lockAnswers = new object (); - private Dictionary answers = new (); + private readonly object _lockAnswers = new (); + private readonly Dictionary _answers = new (); private Label _lblSummary; public override void Main () @@ -27,18 +25,18 @@ public sealed class AnsiEscapeSequenceRequests : Scenario // Init Application.Init (); - TabView tv = new TabView + var tv = new TabView { Width = Dim.Fill (), Height = Dim.Fill () }; - Tab single = new Tab (); - single.DisplayText = "Single"; + var single = new Tab (); + single.DisplayText = "_Single"; single.View = BuildSingleTab (); Tab bulk = new (); - bulk.DisplayText = "Multi"; + bulk.DisplayText = "_Multi"; bulk.View = BuildBulkTab (); tv.AddTab (single, true); @@ -47,7 +45,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario // Setup - Create a top-level application window and configure it. Window appWindow = new () { - Title = GetQuitKeyAndName (), + Title = GetQuitKeyAndName () }; appWindow.Add (tv); @@ -61,18 +59,20 @@ public sealed class AnsiEscapeSequenceRequests : Scenario // Shutdown - Calling Application.Shutdown is required. Application.Shutdown (); } + private View BuildSingleTab () { - View w = new View () + var w = new View { - Width = Dim.Fill(), + Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true }; w.Padding.Thickness = new (1); - var scrRequests = new List + // TODO: This hackery is why I think the EscSeqUtils class should be refactored and the CSI's made type safe. + List scrRequests = new () { "CSI_SendDeviceAttributes", "CSI_ReportTerminalSizeInChars", @@ -80,18 +80,19 @@ public sealed class AnsiEscapeSequenceRequests : Scenario "CSI_SendDeviceAttributes2" }; - var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) }; + var cbRequests = new ComboBox { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) }; w.Add (cbRequests); - var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:" }; + // TODO: Use Pos.Align and Dim.Func so these hardcoded widths aren't needed. + var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "_Request:" }; var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 }; w.Add (label, tfRequest); - label = new Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "Value:" }; + label = new () { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "E_xpectedResponseValue:" }; var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 }; w.Add (label, tfValue); - label = new Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "Terminator:" }; + label = new () { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "_Terminator:" }; var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 }; w.Add (label, tfTerminator); @@ -102,101 +103,111 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return; } - var selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem]; + string selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem]; AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null; + switch (selAnsiEscapeSequenceRequestName) { case "CSI_SendDeviceAttributes": selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes; + break; case "CSI_ReportTerminalSizeInChars": selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportTerminalSizeInChars; + break; case "CSI_RequestCursorPositionReport": selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_RequestCursorPositionReport; + break; case "CSI_SendDeviceAttributes2": selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes2; + break; } tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : ""; - tfValue.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Value ?? "" : ""; + + tfValue.Text = selAnsiEscapeSequenceRequest is { } + ? selAnsiEscapeSequenceRequest.ExpectedResponseValue ?? "" + : ""; tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : ""; }; + // Forces raise cbRequests.SelectedItemChanged to update TextFields cbRequests.SelectedItem = 0; - label = new Label { Y = Pos.Bottom (tfRequest) + 2, Text = "Response:" }; + label = new () { Y = Pos.Bottom (tfRequest) + 2, Text = "_Response:" }; var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; w.Add (label, tvResponse); - label = new Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "Error:" }; + label = new () { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "_Error:" }; var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; w.Add (label, tvError); - label = new Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "Value:" }; + label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "E_xpectedResponseValue:" }; var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true }; w.Add (label, tvValue); - label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "Terminator:" }; + label = new () { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "_Terminator:" }; var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true }; w.Add (label, tvTerminator); - var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request", IsDefault = true }; + var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "_Send Request", IsDefault = true }; var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 }; w.Add (lblSuccess); btnResponse.Accepting += (s, e) => - { - var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest - { - Request = tfRequest.Text, - Terminator = tfTerminator.Text, - Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text - }; + { + var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest + { + Request = tfRequest.Text, + Terminator = tfTerminator.Text, + ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text + }; - var success = AnsiEscapeSequenceRequest.TryExecuteAnsiRequest ( - ansiEscapeSequenceRequest, - out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse - ); + bool success = AnsiEscapeSequenceRequest.TryRequest ( + ansiEscapeSequenceRequest, + out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse + ); - tvResponse.Text = ansiEscapeSequenceResponse.Response; - tvError.Text = ansiEscapeSequenceResponse.Error; - tvValue.Text = ansiEscapeSequenceResponse.Value ?? ""; - tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; + tvResponse.Text = ansiEscapeSequenceResponse.Response; + tvError.Text = ansiEscapeSequenceResponse.Error; + tvValue.Text = ansiEscapeSequenceResponse.ExpectedResponseValue ?? ""; + tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; - if (success) - { - lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"]; - lblSuccess.Text = "Successful"; - } - else - { - lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"]; - lblSuccess.Text = "Error"; - } - }; + if (success) + { + lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"]; + lblSuccess.Text = "Success"; + } + else + { + lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"]; + lblSuccess.Text = "Error"; + } + }; w.Add (btnResponse); - w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "You can send other requests by editing the TextFields." }); + w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "Send other requests by editing the TextFields." }); return w; } private View BuildBulkTab () { - View w = new View () + var w = new View { Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true }; - var lbl = new Label () + var lbl = new Label { - Text = "This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.", + Text = + "_This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.", Height = 2, Width = Dim.Fill () }; @@ -205,50 +216,49 @@ public sealed class AnsiEscapeSequenceRequests : Scenario TimeSpan.FromMilliseconds (1000), () => { - lock (lockAnswers) + lock (_lockAnswers) { UpdateGraph (); UpdateResponses (); } - - return true; }); - var tv = new TextView () + var tv = new TextView { Y = Pos.Bottom (lbl), Width = Dim.Percent (50), Height = Dim.Fill () }; - - var lblDar = new Label () + var lblDar = new Label { Y = Pos.Bottom (lbl), X = Pos.Right (tv) + 1, - Text = "DAR per second", + Text = "_DAR per second: " }; - var cbDar = new NumericUpDown () + + var cbDar = new NumericUpDown { X = Pos.Right (lblDar), Y = Pos.Bottom (lbl), - Value = 0, + Value = 0 }; cbDar.ValueChanging += (s, e) => - { - if (e.NewValue < 0 || e.NewValue > 20) - { - e.Cancel = true; - } - }; + { + if (e.NewValue < 0 || e.NewValue > 20) + { + e.Cancel = true; + } + }; w.Add (cbDar); int lastSendTime = Environment.TickCount; - object lockObj = new object (); + var lockObj = new object (); + Application.AddTimeout ( TimeSpan.FromMilliseconds (50), () => @@ -272,8 +282,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return true; }); - - _graphView = new GraphView () + _graphView = new () { Y = Pos.Bottom (cbDar), X = Pos.Right (tv), @@ -281,7 +290,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Height = Dim.Fill (1) }; - _lblSummary = new Label () + _lblSummary = new () { Y = Pos.Bottom (_graphView), X = Pos.Right (tv), @@ -299,6 +308,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return w; } + private void UpdateResponses () { _lblSummary.Text = GetSummary (); @@ -307,32 +317,31 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private string GetSummary () { - if (answers.Count == 0) + if (_answers.Count == 0) { return "No requests sent yet"; } - var last = answers.Last ().Value; + string last = _answers.Last ().Value; - var unique = answers.Values.Distinct ().Count (); - var total = answers.Count; + int unique = _answers.Values.Distinct ().Count (); + int total = _answers.Count; return $"Last:{last} U:{unique} T:{total}"; } private void SetupGraph () { + _graphView.Series.Add (_sentSeries = new ()); + _graphView.Series.Add (_answeredSeries = new ()); - _graphView.Series.Add (_sentSeries = new ScatterSeries ()); - _graphView.Series.Add (_answeredSeries = new ScatterSeries ()); - - _sentSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightGreen, ColorName16.Black)); - _answeredSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightRed, ColorName16.Black)); + _sentSeries.Fill = new (new ('.'), new (ColorName16.BrightGreen, ColorName16.Black)); + _answeredSeries.Fill = new (new ('.'), new (ColorName16.BrightRed, ColorName16.Black)); // Todo: // _graphView.Annotations.Add (_sentSeries new PathAnnotation {}); - _graphView.CellSize = new PointF (1, 1); + _graphView.CellSize = new (1, 1); _graphView.MarginBottom = 2; _graphView.AxisX.Increment = 1; _graphView.AxisX.Text = "Seconds"; @@ -341,40 +350,37 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void UpdateGraph () { - _sentSeries.Points = sends + _sentSeries.Points = _sends .GroupBy (ToSeconds) .Select (g => new PointF (g.Key, g.Count ())) .ToList (); - _answeredSeries.Points = answers.Keys + _answeredSeries.Points = _answers.Keys .GroupBy (ToSeconds) .Select (g => new PointF (g.Key, g.Count ())) .ToList (); + // _graphView.ScrollOffset = new PointF(,0); if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) { _graphView.SetNeedsDisplay (); } - } - private int ToSeconds (DateTime t) - { - return (int)(DateTime.Now - t).TotalSeconds; - } + private int ToSeconds (DateTime t) { return (int)(DateTime.Now - t).TotalSeconds; } private void SendDar () { - sends.Add (DateTime.Now); - var result = Application.Driver.WriteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes); + _sends.Add (DateTime.Now); + string result = Application.Driver.WriteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes); HandleResponse (result); } private void HandleResponse (string response) { - lock (lockAnswers) + lock (_lockAnswers) { - answers.Add (DateTime.Now, response); + _answers.Add (DateTime.Now, response); } } } From 7d9ae7f341bfab731b5582631b2e2f78f0cf3d95 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Nov 2024 20:48:08 +0000 Subject: [PATCH 076/151] Ad more unit test to handling with IsLetterOrDigit, IsPunctuation and IsSymbol. --- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 4 +- UnitTests/Input/EscSeqUtilsTests.cs | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 99e5bce8f..7b6fe1cf6 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -1448,7 +1448,9 @@ public static class EscSeqUtils } else if ((previousChar != '\u001B' && c <= Key.Space) || (previousChar != '\u001B' && c == 127) || (char.IsLetter (previousChar) && char.IsLower (c) && char.IsLetter (c)) - || (!string.IsNullOrEmpty (split) && split.Length > 2 && char.IsLetter (previousChar) && char.IsLetter (c))) + || (!string.IsNullOrEmpty (split) && split.Length > 2 && char.IsLetter (previousChar) && char.IsLetterOrDigit (c)) + || (!string.IsNullOrEmpty (split) && split.Length > 2 && char.IsLetter (previousChar) && char.IsPunctuation (c)) + || (!string.IsNullOrEmpty (split) && split.Length > 2 && char.IsLetter (previousChar) && char.IsSymbol (c))) { isEscSeq = false; split = AddAndClearSplit (); diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 3b028b60e..7cc225971 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -1441,11 +1441,57 @@ public class EscSeqUtilsTests } [Theory] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\b", "\b")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\t", "\t")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\n", "\n")] [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\r", "\r")] [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCe", "e")] [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCV", "V")] [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\u007f", "\u007f")] [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC ", " ")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\\", "\\")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC|", "|")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC1", "1")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC!", "!")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC\"", "\"")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC@", "@")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC#", "#")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC£", "£")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC$", "$")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC§", "§")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC%", "%")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC€", "€")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC&", "&")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC/", "/")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC{", "{")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC(", "(")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC[", "[")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC)", ")")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC]", "]")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC=", "=")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC}", "}")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC'", "'")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC?", "?")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC«", "«")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC»", "»")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC+", "+")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC*", "*")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC¨", "¨")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC´", "´")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC`", "`")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCç", "ç")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCº", "º")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOCª", "ª")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC~", "~")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC^", "^")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC<", "<")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC>", ">")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC,", ",")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC;", ";")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC.", ".")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC:", ":")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC-", "-")] + [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC_", "_")] public void SplitEscapeRawString_Multiple_Tests (string rawData, string expectedLast) { List splitList = EscSeqUtils.SplitEscapeRawString (rawData); From eb987071c864a3a0e381c20e89fced21e1aed830 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Nov 2024 22:05:46 +0000 Subject: [PATCH 077/151] Revert Key class changes. --- .../WindowsDriver/WindowsDriver.cs | 12 ++++++++- Terminal.Gui/Input/Key.cs | 25 +++++++------------ UnitTests/Input/KeyTests.cs | 10 ++++---- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 6f20539d8..656043f70 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -817,11 +817,21 @@ internal class WindowsDriver : ConsoleDriver // If (ShiftMask is on and CapsLock is off) or (ShiftMask is off and CapsLock is on) add the ShiftMask if (char.IsUpper (keyInfo.KeyChar)) { + if (keyInfo.KeyChar <= 'Z') + { + return (KeyCode)keyInfo.Key | KeyCode.ShiftMask; + } + // Always return the KeyChar because it may be an Á, À with Oem1, etc - return (KeyCode)keyInfo.KeyChar | KeyCode.ShiftMask; + return (KeyCode)keyInfo.KeyChar; } } + if (keyInfo.KeyChar <= 'z') + { + return (KeyCode)keyInfo.Key; + } + // Always return the KeyChar because it may be an á, à with Oem1, etc return (KeyCode)keyInfo.KeyChar; } diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index 3c8b75d94..f5cd3e8bc 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; -using Terminal.Gui.ConsoleDrivers; namespace Terminal.Gui; @@ -209,13 +208,13 @@ public class Key : EventArgs, IEquatable get => _keyCode; init { -//#if DEBUG -// if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0) -// { -// throw new ArgumentException ($"Invalid KeyCode: {value} is invalid.", nameof (value)); -// } +#if DEBUG + if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0) + { + throw new ArgumentException ($"Invalid KeyCode: {value} is invalid.", nameof (value)); + } -//#endif +#endif _keyCode = value; } } @@ -289,10 +288,7 @@ public class Key : EventArgs, IEquatable return false; } - // A to Z may have , , , , with Oem1, etc - ConsoleKeyInfo cki = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (keyCode); - - if (cki.Key is >= ConsoleKey.A and <= ConsoleKey.Z) + if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z) { return true; } @@ -525,12 +521,9 @@ public class Key : EventArgs, IEquatable // Handle special cases and modifiers on their own if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers)) { - // A to Z may have , , , , with Oem1, etc - ConsoleKeyInfo cki = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (baseKey); - - if ((key & KeyCode.SpecialMask) != 0 && cki.Key is >= ConsoleKey.A and <= ConsoleKey.Z) + if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) { - sb.Append (((char)(baseKey & ~KeyCode.Space)).ToString ()); + sb.Append (baseKey & ~KeyCode.Space); } else { diff --git a/UnitTests/Input/KeyTests.cs b/UnitTests/Input/KeyTests.cs index 1916c1d9b..cf4cb7858 100644 --- a/UnitTests/Input/KeyTests.cs +++ b/UnitTests/Input/KeyTests.cs @@ -17,7 +17,7 @@ public class KeyTests { "Alt+A", Key.A.WithAlt }, { "Shift+A", Key.A.WithShift }, { "A", Key.A.WithShift }, - { "â", new ((KeyCode)'Â') }, + { "â", new ((KeyCode)'â') }, { "Shift+â", new ((KeyCode)'â' | KeyCode.ShiftMask) }, { "Shift+Â", new ((KeyCode)'Â' | KeyCode.ShiftMask) }, { "Ctrl+Shift+CursorUp", Key.CursorUp.WithShift.WithCtrl }, @@ -342,10 +342,10 @@ public class KeyTests [InlineData ((KeyCode)'{', "{")] [InlineData ((KeyCode)'\'', "\'")] [InlineData ((KeyCode)'ó', "ó")] - [InlineData ((KeyCode)'Ó' | KeyCode.ShiftMask, "Ó")] - [InlineData ((KeyCode)'ó' | KeyCode.ShiftMask, "Ó")] + [InlineData ((KeyCode)'Ó' | KeyCode.ShiftMask, "Shift+Ó")] + [InlineData ((KeyCode)'ó' | KeyCode.ShiftMask, "Shift+ó")] [InlineData ((KeyCode)'Ó', "Ó")] - [InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+Ç")] + [InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+ç")] [InlineData ((KeyCode)'a', "a")] // 97 or Key.Space | Key.A [InlineData ((KeyCode)'A', "a")] // 65 or equivalent to Key.A, but A-Z are mapped to lower case by drivers [InlineData (KeyCode.ShiftMask | KeyCode.A, "A")] @@ -468,7 +468,7 @@ public class KeyTests [InlineData ("Alt+A", KeyCode.A | KeyCode.AltMask)] [InlineData ("Shift+A", KeyCode.A | KeyCode.ShiftMask)] [InlineData ("A", KeyCode.A | KeyCode.ShiftMask)] - [InlineData ("â", (KeyCode)'Â')] + [InlineData ("â", (KeyCode)'â')] [InlineData ("Shift+â", (KeyCode)'â' | KeyCode.ShiftMask)] [InlineData ("Shift+Â", (KeyCode)'Â' | KeyCode.ShiftMask)] [InlineData ("Ctrl+Shift+CursorUp", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.CursorUp)] From 0b11e20e2fadb34c524f165a1de881997b2bb44b Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Nov 2024 22:36:58 +0000 Subject: [PATCH 078/151] Change Response to a nullable string. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 2 +- Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 1 - Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 1 - Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 2e8da6bfe..0d35d4a92 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -24,7 +24,7 @@ public class AnsiEscapeSequenceRequest /// /// Gets the response received from the request. /// - public string Response { get; internal set; } = string.Empty; + public string? Response { get; internal set; } /// /// Raised when the console responds with an ANSI response code that matches the diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index d6ccf9991..885f907a0 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -45,7 +45,7 @@ public abstract class ConsoleDriver /// /// The object. /// The request response. - public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); + public abstract string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API? diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 52014cb55..4410b799d 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -248,7 +248,6 @@ internal class UnixMainLoop : IMainLoopDriver { EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.Response = string.Empty; seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); } } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 939ed0d2d..ed2ddd7f5 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -94,7 +94,6 @@ internal class NetEvents : IDisposable { EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.Response = string.Empty; seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 22c0e1035..d031817ee 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -992,7 +992,6 @@ internal class WindowsConsole { _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.Response = string.Empty; seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); } } From bdcc0ff6d43b6e0a6ea91aeb7388340ed838102c Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Nov 2024 23:36:21 +0000 Subject: [PATCH 079/151] Return null instead if empty and fix a bug that was returning the terminator as input. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 4 ++-- Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 2 +- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 2 +- Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 6 +++++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 0d35d4a92..4c1636bf0 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -126,8 +126,8 @@ public class AnsiEscapeSequenceRequest /// public string? ExpectedResponseValue { get; init; } - internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string response) { ResponseFromInput?.Invoke (ansiRequest, response); } + internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string? response) { ResponseFromInput?.Invoke (ansiRequest, response); } // QUESTION: What is this for? Please provide a descriptive comment. - internal event EventHandler? ResponseFromInput; + internal event EventHandler? ResponseFromInput; } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 4410b799d..9152296ff 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -248,7 +248,7 @@ internal class UnixMainLoop : IMainLoopDriver { EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null); } } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index ed2ddd7f5..6c4c24d1f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -94,7 +94,7 @@ internal class NetEvents : IDisposable { EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null); } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index d031817ee..1507ed4f4 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -978,6 +978,8 @@ internal class WindowsConsole { seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); } _retries = 0; @@ -992,7 +994,9 @@ internal class WindowsConsole { _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); } } From 629cea841d4eb93138e6d2b77e1f93b30e474b6c Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 00:05:43 +0000 Subject: [PATCH 080/151] Handling null response. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 7 ++++++- .../AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs | 2 +- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 4c1636bf0..a820ce944 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -83,7 +83,12 @@ public class AnsiEscapeSequenceRequest throw new InvalidOperationException ("Terminator request is empty."); } - if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) + if (string.IsNullOrEmpty (ansiRequest.Response)) + { + throw new InvalidOperationException ("Response request is null."); + } + + if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) { string resp = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response.Last ().ToString (); diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs index 2cc820801..9a5a2ec8d 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs @@ -24,7 +24,7 @@ public class AnsiEscapeSequenceResponse /// /// . /// - public required string Response { get; init; } + public required string? Response { get; init; } // QUESTION: Does string.Empty indicate no terminator expected? If not, perhaps make this property nullable? /// diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index f88c80820..62fd1ef60 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -172,7 +172,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse ); - tvResponse.Text = ansiEscapeSequenceResponse.Response; + tvResponse.Text = ansiEscapeSequenceResponse.Response ?? ""; tvError.Text = ansiEscapeSequenceResponse.Error; tvValue.Text = ansiEscapeSequenceResponse.ExpectedResponseValue ?? ""; tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; From f87c2b1e835235062f5a4001437584918d3cbc07 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 00:37:33 +0000 Subject: [PATCH 081/151] Rename EscSeq to AnsiEscapeSequence and move to his folder. --- .../AnsiEscapeSequenceRequest.cs | 4 +- .../AnsiEscapeSequenceRequestStatus.cs} | 6 +- .../AnsiEscapeSequenceRequestUtils.cs} | 6 +- .../AnsiEscapeSequenceRequests.cs} | 22 +- .../CursesDriver/CursesDriver.cs | 18 +- .../CursesDriver/UnixMainLoop.cs | 38 +-- .../ConsoleDrivers/NetDriver/NetDriver.cs | 32 +-- .../ConsoleDrivers/NetDriver/NetEvents.cs | 34 +-- .../WindowsDriver/WindowsConsole.cs | 22 +- .../WindowsDriver/WindowsDriver.cs | 14 +- .../Scenarios/AnsiEscapeSequenceRequests.cs | 10 +- ...=> AnsiEscapeSequenceRequestUtilsTests.cs} | 262 +++++++++--------- ....cs => AnsiEscapeSequenceRequestsTests.cs} | 14 +- 13 files changed, 241 insertions(+), 241 deletions(-) rename Terminal.Gui/ConsoleDrivers/{EscSeqUtils/EscSeqReqStatus.cs => AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs} (71%) rename Terminal.Gui/ConsoleDrivers/{EscSeqUtils/EscSeqUtils.cs => AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs} (99%) rename Terminal.Gui/ConsoleDrivers/{EscSeqUtils/EscSeqReq.cs => AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs} (72%) rename UnitTests/Input/{EscSeqUtilsTests.cs => AnsiEscapeSequenceRequestUtilsTests.cs} (84%) rename UnitTests/Input/{EscSeqReqTests.cs => AnsiEscapeSequenceRequestsTests.cs} (82%) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index a820ce944..1159ae37d 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -73,7 +73,7 @@ public class AnsiEscapeSequenceRequest // Send the ANSI escape sequence ansiRequest.Response = driver?.WriteAnsiRequest (ansiRequest)!; - if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (EscSeqUtils.KeyEsc)) + if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc)) { throw new InvalidOperationException ($"Invalid Response: {ansiRequest.Response}"); } @@ -103,7 +103,7 @@ public class AnsiEscapeSequenceRequest { if (string.IsNullOrEmpty (error.ToString ())) { - (string? _, string? _, values, string? _) = EscSeqUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ()); + (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ()); } } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs similarity index 71% rename from Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs rename to Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs index c9561ec7b..b07d48236 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs @@ -3,14 +3,14 @@ namespace Terminal.Gui; /// /// Represents the status of an ANSI escape sequence request made to the terminal using -/// . +/// . /// /// -public class EscSeqReqStatus +public class AnsiEscapeSequenceRequestStatus { /// Creates a new state of escape sequence request. /// The object. - public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; } + public AnsiEscapeSequenceRequestStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; } /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). public AnsiEscapeSequenceRequest AnsiRequest { get; } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs similarity index 99% rename from Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs rename to Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index 7b6fe1cf6..844112d65 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -19,7 +19,7 @@ namespace Terminal.Gui; /// * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html /// * https://vt100.net/ /// -public static class EscSeqUtils +public static class AnsiEscapeSequenceRequestUtils { // TODO: One type per file - Move this enum to a separate file. /// @@ -197,7 +197,7 @@ public static class EscSeqUtils /// The object. /// The handler that will process the event. public static void DecodeEscSeq ( - EscSeqRequests? escSeqRequests, + AnsiEscapeSequenceRequests? escSeqRequests, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, @@ -209,7 +209,7 @@ public static class EscSeqUtils out bool isMouse, out List buttonState, out Point pos, - out EscSeqReqStatus? seqReqStatus, + out AnsiEscapeSequenceRequestStatus? seqReqStatus, Action continuousButtonPressedHandler ) { diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs similarity index 72% rename from Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs rename to Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs index 280f3f9e7..8cc984523 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs @@ -8,14 +8,14 @@ namespace Terminal.Gui; // TODO: This class is a singleton. It should use the singleton pattern. /// -/// Manages ANSI Escape Sequence requests and responses. The list of contains the +/// Manages ANSI Escape Sequence requests and responses. The list of contains the /// status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator). /// -public class EscSeqRequests +public class AnsiEscapeSequenceRequests { /// /// Adds a new request for the ANSI Escape Sequence defined by . Adds a - /// instance to list. + /// instance to list. /// /// The object. /// The driver in use. @@ -38,13 +38,13 @@ public class EscSeqRequests } /// - /// Indicates if a with the exists in the + /// Indicates if a with the exists in the /// list. /// /// /// /// if exist, otherwise. - public bool HasResponse (string terminator, out EscSeqReqStatus? seqReqStatus) + public bool HasResponse (string terminator, out AnsiEscapeSequenceRequestStatus? seqReqStatus) { lock (Statuses) { @@ -64,13 +64,13 @@ public class EscSeqRequests } /// - /// Removes a request defined by . If a matching is + /// Removes a request defined by . If a matching is /// found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented. - /// If the number of outstanding requests is 0, the is removed from + /// If the number of outstanding requests is 0, the is removed from /// . /// - /// The object. - public void Remove (EscSeqReqStatus? seqReqStatus) + /// The object. + public void Remove (AnsiEscapeSequenceRequestStatus? seqReqStatus) { lock (Statuses) { @@ -83,6 +83,6 @@ public class EscSeqRequests } } - /// Gets the list. - public ConcurrentQueue Statuses { get; } = new (); + /// Gets the list. + public ConcurrentQueue Statuses { get; } = new (); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index e0225b3aa..80b74edf6 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -230,7 +230,7 @@ internal class CursesDriver : ConsoleDriver redrawAttr = attr; output.Append ( - EscSeqUtils.CSI_SetForegroundColorRGB ( + AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB ( attr.Foreground.R, attr.Foreground.G, attr.Foreground.B @@ -238,7 +238,7 @@ internal class CursesDriver : ConsoleDriver ); output.Append ( - EscSeqUtils.CSI_SetBackgroundColorRGB ( + AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB ( attr.Background.R, attr.Background.G, attr.Background.B @@ -487,8 +487,8 @@ internal class CursesDriver : ConsoleDriver if (visibility != CursorVisibility.Invisible) { Console.Out.Write ( - EscSeqUtils.CSI_SetCursorStyle ( - (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) + AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( + (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF) ) ); @@ -515,7 +515,7 @@ internal class CursesDriver : ConsoleDriver } else { - _mainLoopDriver.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + _mainLoopDriver.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); } } } @@ -647,7 +647,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); } } @@ -655,7 +655,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); } } @@ -665,7 +665,7 @@ internal class CursesDriver : ConsoleDriver { // + 1 is needed because non-Windows is based on 1 instead of 0 and // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (row + 1, col + 1)); return true; } @@ -903,7 +903,7 @@ internal class CursesDriver : ConsoleDriver { _mainLoopDriver._forceRead = false; - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request)) { if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 9152296ff..68d6df13a 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -52,7 +52,7 @@ internal class UnixMainLoop : IMainLoopDriver _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); } - public EscSeqRequests EscSeqRequests { get; } = new (); + public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); void IMainLoopDriver.Wakeup () { _eventReady.Set (); } @@ -77,7 +77,7 @@ internal class UnixMainLoop : IMainLoopDriver throw new NotSupportedException ("libc not found", e); } - EscSeqUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed; + AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed; Task.Run (CursesInputHandler, _inputHandlerTokenSource.Token); Task.Run (WindowSizeHandler, _inputHandlerTokenSource.Token); @@ -213,10 +213,10 @@ internal class UnixMainLoop : IMainLoopDriver // Convert the byte array to a string (assuming UTF-8 encoding) string data = Encoding.UTF8.GetString (buffer); - if (EscSeqUtils.IncompleteCkInfos is { }) + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) { - data = data.Insert (0, EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos)); - EscSeqUtils.IncompleteCkInfos = null; + data = data.Insert (0, AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos)); + AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; } // Enqueue the data @@ -238,11 +238,11 @@ internal class UnixMainLoop : IMainLoopDriver break; } - if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -284,7 +284,7 @@ internal class UnixMainLoop : IMainLoopDriver private void ProcessEnqueuePollData (string pollData) { - foreach (string split in EscSeqUtils.SplitEscapeRawString (pollData)) + foreach (string split in AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (pollData)) { EnqueuePollData (split); } @@ -292,13 +292,13 @@ internal class UnixMainLoop : IMainLoopDriver private void EnqueuePollData (string pollDataPart) { - ConsoleKeyInfo [] cki = EscSeqUtils.ToConsoleKeyInfoArray (pollDataPart); + ConsoleKeyInfo [] cki = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (pollDataPart); ConsoleKey key = 0; ConsoleModifiers mod = 0; ConsoleKeyInfo newConsoleKeyInfo = default; - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( EscSeqRequests, ref newConsoleKeyInfo, ref key, @@ -311,8 +311,8 @@ internal class UnixMainLoop : IMainLoopDriver out bool isMouse, out List mouseFlags, out Point pos, - out EscSeqReqStatus seqReqStatus, - EscSeqUtils.ProcessMouseEvent + out AnsiEscapeSequenceRequestStatus seqReqStatus, + AnsiEscapeSequenceRequestUtils.ProcessMouseEvent ); if (isMouse) @@ -327,7 +327,7 @@ internal class UnixMainLoop : IMainLoopDriver if (seqReqStatus is { }) { - var ckiString = EscSeqUtils.ToString (cki); + var ckiString = AnsiEscapeSequenceRequestUtils.ToString (cki); lock (seqReqStatus.AnsiRequest._responseLock) { @@ -338,16 +338,16 @@ internal class UnixMainLoop : IMainLoopDriver return; } - if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator)) + if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) { - if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result)) + if (EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus result)) { lock (result.AnsiRequest._responseLock) { - result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator; - result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator); + result.AnsiRequest.Response = AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator; + result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); - EscSeqUtils.InvalidRequestTerminator = null; + AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null; } } @@ -425,7 +425,7 @@ internal class UnixMainLoop : IMainLoopDriver void IMainLoopDriver.TearDown () { - EscSeqUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; + AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; _inputHandlerTokenSource?.Cancel (); _inputHandlerTokenSource?.Dispose (); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index e4103d8c3..d650c91f0 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -37,15 +37,15 @@ internal class NetDriver : ConsoleDriver Console.Clear (); //Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); //Set cursor key to cursor. - Console.Out.Write (EscSeqUtils.CSI_ShowCursor); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_ShowCursor); Platform.Suspend (); //Enable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); SetContentsAsDirty (); Refresh (); @@ -139,7 +139,7 @@ internal class NetDriver : ConsoleDriver if (Force16Colors) { output.Append ( - EscSeqUtils.CSI_SetGraphicsRendition ( + AnsiEscapeSequenceRequestUtils.CSI_SetGraphicsRendition ( MapColors ( (ConsoleColor)attr.Background.GetClosestNamedColor16 (), false @@ -151,7 +151,7 @@ internal class NetDriver : ConsoleDriver else { output.Append ( - EscSeqUtils.CSI_SetForegroundColorRGB ( + AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB ( attr.Foreground.R, attr.Foreground.G, attr.Foreground.B @@ -159,7 +159,7 @@ internal class NetDriver : ConsoleDriver ); output.Append ( - EscSeqUtils.CSI_SetBackgroundColorRGB ( + AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB ( attr.Background.R, attr.Background.G, attr.Background.B @@ -277,10 +277,10 @@ internal class NetDriver : ConsoleDriver Rows = Console.WindowHeight; //Enable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); //Set cursor key to application. - Console.Out.Write (EscSeqUtils.CSI_HideCursor); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_HideCursor); } else { @@ -374,10 +374,10 @@ internal class NetDriver : ConsoleDriver Console.ResetColor (); //Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); //Set cursor key to cursor. - Console.Out.Write (EscSeqUtils.CSI_ShowCursor); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_ShowCursor); Console.Out.Close (); } } @@ -467,7 +467,7 @@ internal class NetDriver : ConsoleDriver // + 1 is needed because non-Windows is based on 1 instead of 0 and // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (row + 1, col + 1)); return true; } @@ -496,7 +496,7 @@ internal class NetDriver : ConsoleDriver { _cachedCursorVisibility = visibility; - Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + Console.Out.Write (visibility == CursorVisibility.Default ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor); return visibility == CursorVisibility.Default; } @@ -525,7 +525,7 @@ internal class NetDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); } } @@ -533,7 +533,7 @@ internal class NetDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); } } @@ -875,7 +875,7 @@ internal class NetDriver : ConsoleDriver { _mainLoopDriver._netEvents._forceRead = false; - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request)) { if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) @@ -954,7 +954,7 @@ internal class NetDriver : ConsoleDriver } else { - Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols)); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetTerminalWindowSize (Rows, Cols)); } // CONCURRENCY: Unsynchronized access to Clip is not safe. diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 6c4c24d1f..3e5414241 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -19,7 +19,7 @@ internal class NetEvents : IDisposable #if PROCESS_REQUEST bool _neededProcessRequest; #endif - public EscSeqRequests EscSeqRequests { get; } = new (); + public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); public NetEvents (ConsoleDriver consoleDriver) { @@ -84,11 +84,11 @@ internal class NetEvents : IDisposable return Console.ReadKey (intercept); } - if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -161,9 +161,9 @@ internal class NetEvents : IDisposable return; } - if (EscSeqUtils.IncompleteCkInfos is { }) + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) { - EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki); + AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); } if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) @@ -171,7 +171,7 @@ internal class NetEvents : IDisposable { if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) { - _cki = EscSeqUtils.ResizeArray ( + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( new ConsoleKeyInfo ( (char)KeyCode.Esc, 0, @@ -199,7 +199,7 @@ internal class NetEvents : IDisposable else { newConsoleKeyInfo = consoleKeyInfo; - _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); if (Console.KeyAvailable) { @@ -221,7 +221,7 @@ internal class NetEvents : IDisposable if (Console.KeyAvailable) { - _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); } else { @@ -250,7 +250,7 @@ internal class NetEvents : IDisposable _inputQueue.Enqueue ( new InputResult { - EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo) + EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo) } ); _isEscSeq = false; @@ -346,7 +346,7 @@ internal class NetEvents : IDisposable ) { // isMouse is true if it's CSI<, false otherwise - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( EscSeqRequests, ref newConsoleKeyInfo, ref key, @@ -359,7 +359,7 @@ internal class NetEvents : IDisposable out bool isMouse, out List mouseFlags, out Point pos, - out EscSeqReqStatus seqReqStatus, + out AnsiEscapeSequenceRequestStatus seqReqStatus, (f, p) => HandleMouseEvent (MapMouseFlags (f), p) ); @@ -377,7 +377,7 @@ internal class NetEvents : IDisposable { //HandleRequestResponseEvent (c1Control, code, values, terminating); - var ckiString = EscSeqUtils.ToString (cki); + var ckiString = AnsiEscapeSequenceRequestUtils.ToString (cki); lock (seqReqStatus.AnsiRequest._responseLock) { @@ -388,16 +388,16 @@ internal class NetEvents : IDisposable return; } - if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator)) + if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) { - if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result)) + if (EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus result)) { lock (result.AnsiRequest._responseLock) { - result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator; - result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator); + result.AnsiRequest.Response = AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator; + result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); - EscSeqUtils.InvalidRequestTerminator = null; + AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null; } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 1507ed4f4..430085a2a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -68,8 +68,8 @@ internal class WindowsConsole { _stringBuilder.Clear (); - _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); + _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorPosition); + _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (0, 0)); Attribute? prev = null; @@ -80,8 +80,8 @@ internal class WindowsConsole if (attr != prev) { prev = attr; - _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); - _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); + _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); + _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); } if (info.Char != '\x1b') @@ -97,8 +97,8 @@ internal class WindowsConsole } } - _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); - _stringBuilder.Append (EscSeqUtils.CSI_HideCursor); + _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorPosition); + _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_HideCursor); var s = _stringBuilder.ToString (); @@ -949,7 +949,7 @@ internal class WindowsConsole ansiSequence.Append (inputChar); // Check if the sequence has ended with an expected command terminator - if (_mainLoop.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out EscSeqReqStatus seqReqStatus)) + if (_mainLoop.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus seqReqStatus)) { // Finished reading the sequence and remove the enqueued request _mainLoop.EscSeqRequests.Remove (seqReqStatus); @@ -970,9 +970,9 @@ internal class WindowsConsole } } - if (readingSequence && !raisedResponse && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus); + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus seqReqStatus); lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -984,11 +984,11 @@ internal class WindowsConsole _retries = 0; } - else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) { if (_retries > 1) { - if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { lock (seqReqStatus!.AnsiRequest._responseLock) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 656043f70..9372fcb04 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -242,7 +242,7 @@ internal class WindowsDriver : ConsoleDriver { _mainLoopDriver._forceRead = false; - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request)) + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request)) { if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) @@ -316,7 +316,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); + sb.Append (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); WinConsole?.WriteANSI (sb.ToString ()); } @@ -352,7 +352,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + sb.Append (visibility != CursorVisibility.Invisible ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor); return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } } @@ -367,7 +367,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor); return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } @@ -488,7 +488,7 @@ internal class WindowsDriver : ConsoleDriver if (!RunningUnitTests && _isWindowsTerminal) { // Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); } } @@ -513,7 +513,7 @@ internal class WindowsDriver : ConsoleDriver if (_isWindowsTerminal) { - Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); } } catch (Win32Exception e) @@ -1291,7 +1291,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - public EscSeqRequests EscSeqRequests { get; } = new (); + public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); void IMainLoopDriver.Setup (MainLoop mainLoop) { diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 62fd1ef60..af8b0d868 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -109,19 +109,19 @@ public sealed class AnsiEscapeSequenceRequests : Scenario switch (selAnsiEscapeSequenceRequestName) { case "CSI_SendDeviceAttributes": - selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes; + selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; break; case "CSI_ReportTerminalSizeInChars": - selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportTerminalSizeInChars; + selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_ReportTerminalSizeInChars; break; case "CSI_RequestCursorPositionReport": - selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_RequestCursorPositionReport; + selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_RequestCursorPositionReport; break; case "CSI_SendDeviceAttributes2": - selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes2; + selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes2; break; } @@ -372,7 +372,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void SendDar () { _sends.Add (DateTime.Now); - string result = Application.Driver.WriteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes); + string result = Application.Driver.WriteAnsiRequest (AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes); HandleResponse (result); } diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs similarity index 84% rename from UnitTests/Input/EscSeqUtilsTests.cs rename to UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs index 7cc225971..dbfc5286e 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs @@ -3,17 +3,17 @@ namespace Terminal.Gui.InputTests; -public class EscSeqUtilsTests +public class AnsiEscapeSequenceRequestUtilsTests { private bool _actionStarted; private MouseFlags _arg1; private Point _arg2; private string _c1Control, _code, _terminating; private ConsoleKeyInfo [] _cki; - private EscSeqRequests _escSeqReqProc; + private AnsiEscapeSequenceRequests _escSeqReqProc; private bool _isKeyMouse; [CanBeNull] - private EscSeqReqStatus _seqReqStatus; + private AnsiEscapeSequenceRequestStatus _seqReqStatus; private ConsoleKey _key; private ConsoleModifiers _mod; private List _mouseFlags; @@ -29,7 +29,7 @@ public class EscSeqUtilsTests _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false) }; var expectedCki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -64,7 +64,7 @@ public class EscSeqUtilsTests _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false), new ('\u0012', 0, false, false, false) }; expectedCki = new ('\u0012', ConsoleKey.R, false, true, true); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -99,7 +99,7 @@ public class EscSeqUtilsTests _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false), new ('r', 0, false, false, false) }; expectedCki = new ('r', ConsoleKey.R, false, true, false); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -139,7 +139,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, false, false); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -185,7 +185,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, false, false); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -231,7 +231,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, true, false); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -277,7 +277,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, true, false); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -323,7 +323,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, false, true); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -369,7 +369,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, false, true); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -415,7 +415,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, true, true); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -461,7 +461,7 @@ public class EscSeqUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, true, true); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -510,7 +510,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -560,7 +560,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -615,7 +615,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -663,7 +663,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -718,7 +718,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -787,7 +787,7 @@ public class EscSeqUtilsTests }; expectedCki = default (ConsoleKeyInfo); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -844,7 +844,7 @@ public class EscSeqUtilsTests Assert.Single (_escSeqReqProc.Statuses); Assert.Equal ("t", _escSeqReqProc.Statuses.ToArray () [^1].AnsiRequest.Terminator); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -908,7 +908,7 @@ public class EscSeqUtilsTests var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -975,7 +975,7 @@ public class EscSeqUtilsTests ConsoleKeyInfo expectedCki = default; - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -1005,10 +1005,10 @@ public class EscSeqUtilsTests Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); - Assert.Equal (_cki, EscSeqUtils.IncompleteCkInfos); + Assert.Equal (_cki, AnsiEscapeSequenceRequestUtils.IncompleteCkInfos); - _cki = EscSeqUtils.InsertArray ( - EscSeqUtils.IncompleteCkInfos, + _cki = AnsiEscapeSequenceRequestUtils.InsertArray ( + AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, [ new ('0', 0, false, false, false), new (';', 0, false, false, false), @@ -1019,7 +1019,7 @@ public class EscSeqUtilsTests expectedCki = default; - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -1050,8 +1050,8 @@ public class EscSeqUtilsTests Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); - Assert.NotEqual (_cki, EscSeqUtils.IncompleteCkInfos); - Assert.Contains (EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos), EscSeqUtils.ToString (_cki)); + Assert.NotEqual (_cki, AnsiEscapeSequenceRequestUtils.IncompleteCkInfos); + Assert.Contains (AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos), AnsiEscapeSequenceRequestUtils.ToString (_cki)); ClearAll (); } @@ -1072,7 +1072,7 @@ public class EscSeqUtilsTests _cki = [new (keyChar, 0, false, false, false)]; var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - EscSeqUtils.DecodeEscSeq ( + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( _escSeqReqProc, ref _newConsoleKeyInfo, ref _key, @@ -1136,38 +1136,38 @@ public class EscSeqUtilsTests [Fact] public void Defaults_Values () { - Assert.Equal ('\x1b', EscSeqUtils.KeyEsc); - Assert.Equal ("\x1b[", EscSeqUtils.CSI); - Assert.Equal ("\x1b[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse); - Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse); - Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse); - Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse); - Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse); - Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse); - Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.CSI_EnableMouseEvents); - Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.CSI_DisableMouseEvents); + Assert.Equal ('\x1b', AnsiEscapeSequenceRequestUtils.KeyEsc); + Assert.Equal ("\x1b[", AnsiEscapeSequenceRequestUtils.CSI); + Assert.Equal ("\x1b[?1003h", AnsiEscapeSequenceRequestUtils.CSI_EnableAnyEventMouse); + Assert.Equal ("\x1b[?1006h", AnsiEscapeSequenceRequestUtils.CSI_EnableSgrExtModeMouse); + Assert.Equal ("\x1b[?1015h", AnsiEscapeSequenceRequestUtils.CSI_EnableUrxvtExtModeMouse); + Assert.Equal ("\x1b[?1003l", AnsiEscapeSequenceRequestUtils.CSI_DisableAnyEventMouse); + Assert.Equal ("\x1b[?1006l", AnsiEscapeSequenceRequestUtils.CSI_DisableSgrExtModeMouse); + Assert.Equal ("\x1b[?1015l", AnsiEscapeSequenceRequestUtils.CSI_DisableUrxvtExtModeMouse); + Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); + Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); } [Fact] public void GetC1ControlChar_Tests () { - Assert.Equal ("IND", EscSeqUtils.GetC1ControlChar ('D')); - Assert.Equal ("NEL", EscSeqUtils.GetC1ControlChar ('E')); - Assert.Equal ("HTS", EscSeqUtils.GetC1ControlChar ('H')); - Assert.Equal ("RI", EscSeqUtils.GetC1ControlChar ('M')); - Assert.Equal ("SS2", EscSeqUtils.GetC1ControlChar ('N')); - Assert.Equal ("SS3", EscSeqUtils.GetC1ControlChar ('O')); - Assert.Equal ("DCS", EscSeqUtils.GetC1ControlChar ('P')); - Assert.Equal ("SPA", EscSeqUtils.GetC1ControlChar ('V')); - Assert.Equal ("EPA", EscSeqUtils.GetC1ControlChar ('W')); - Assert.Equal ("SOS", EscSeqUtils.GetC1ControlChar ('X')); - Assert.Equal ("DECID", EscSeqUtils.GetC1ControlChar ('Z')); - Assert.Equal ("CSI", EscSeqUtils.GetC1ControlChar ('[')); - Assert.Equal ("ST", EscSeqUtils.GetC1ControlChar ('\\')); - Assert.Equal ("OSC", EscSeqUtils.GetC1ControlChar (']')); - Assert.Equal ("PM", EscSeqUtils.GetC1ControlChar ('^')); - Assert.Equal ("APC", EscSeqUtils.GetC1ControlChar ('_')); - Assert.Equal ("", EscSeqUtils.GetC1ControlChar ('\0')); + Assert.Equal ("IND", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('D')); + Assert.Equal ("NEL", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('E')); + Assert.Equal ("HTS", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('H')); + Assert.Equal ("RI", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('M')); + Assert.Equal ("SS2", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('N')); + Assert.Equal ("SS3", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('O')); + Assert.Equal ("DCS", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('P')); + Assert.Equal ("SPA", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('V')); + Assert.Equal ("EPA", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('W')); + Assert.Equal ("SOS", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('X')); + Assert.Equal ("DECID", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('Z')); + Assert.Equal ("CSI", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('[')); + Assert.Equal ("ST", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('\\')); + Assert.Equal ("OSC", AnsiEscapeSequenceRequestUtils.GetC1ControlChar (']')); + Assert.Equal ("PM", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('^')); + Assert.Equal ("APC", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('_')); + Assert.Equal ("", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('\0')); } [Fact] @@ -1175,51 +1175,51 @@ public class EscSeqUtilsTests { var cki = new ConsoleKeyInfo ('r', 0, false, false, false); var expectedCki = new ConsoleKeyInfo ('r', ConsoleKey.R, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, false, false); expectedCki = new ('r', ConsoleKey.R, true, false, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, true, false); expectedCki = new ('r', ConsoleKey.R, false, true, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, false, true); expectedCki = new ('r', ConsoleKey.R, false, false, true); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, true, false); expectedCki = new ('r', ConsoleKey.R, true, true, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, true, true); expectedCki = new ('r', ConsoleKey.R, false, true, true); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, true, true); expectedCki = new ('r', ConsoleKey.R, true, true, true); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('\u0012', 0, false, false, false); expectedCki = new ('\u0012', ConsoleKey.R, false, false, true); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('\0', (ConsoleKey)64, false, false, true); expectedCki = new ('\0', ConsoleKey.Spacebar, false, false, true); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('\r', 0, false, false, false); expectedCki = new ('\r', ConsoleKey.Enter, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('\u007f', 0, false, false, false); expectedCki = new ('\u007f', ConsoleKey.Backspace, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); cki = new ('R', 0, false, false, false); expectedCki = new ('R', ConsoleKey.R, true, false, false); - Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); } [Fact] @@ -1227,69 +1227,69 @@ public class EscSeqUtilsTests { ConsoleModifiers mod = 0; char keyChar = '\0'; - Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('A', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('B', "", ref mod, ref keyChar)); - Assert.Equal (_key = ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('C', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('D', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('F', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('H', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F1, EscSeqUtils.GetConsoleKey ('P', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F2, EscSeqUtils.GetConsoleKey ('Q', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F3, EscSeqUtils.GetConsoleKey ('R', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F4, EscSeqUtils.GetConsoleKey ('S', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Tab, EscSeqUtils.GetConsoleKey ('Z', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.UpArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('A', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('B', "", ref mod, ref keyChar)); + Assert.Equal (_key = ConsoleKey.RightArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('C', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('D', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('F', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('H', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F1, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('P', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F2, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('Q', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F3, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('R', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F4, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('S', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Tab, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('Z', "", ref mod, ref keyChar)); Assert.Equal (ConsoleModifiers.Shift, mod); - Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('\0', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('~', "2", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Delete, EscSeqUtils.GetConsoleKey ('~', "3", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('~', "5", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('~', "6", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F5, EscSeqUtils.GetConsoleKey ('~', "15", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F6, EscSeqUtils.GetConsoleKey ('~', "17", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F7, EscSeqUtils.GetConsoleKey ('~', "18", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F8, EscSeqUtils.GetConsoleKey ('~', "19", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F9, EscSeqUtils.GetConsoleKey ('~', "20", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F10, EscSeqUtils.GetConsoleKey ('~', "21", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F11, EscSeqUtils.GetConsoleKey ('~', "23", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F12, EscSeqUtils.GetConsoleKey ('~', "24", ref mod, ref keyChar)); - Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('~', "", ref mod, ref keyChar)); + Assert.Equal (0, (int)AnsiEscapeSequenceRequestUtils.GetConsoleKey ('\0', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "2", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Delete, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "3", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "5", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "6", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F5, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "15", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F6, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "17", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F7, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "18", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F8, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "19", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F9, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "20", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F10, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "21", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F11, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "23", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F12, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "24", ref mod, ref keyChar)); + Assert.Equal (0, (int)AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "", ref mod, ref keyChar)); // These terminators are used by macOS on a numeric keypad without keys modifiers - Assert.Equal (ConsoleKey.Add, EscSeqUtils.GetConsoleKey ('l', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Subtract, EscSeqUtils.GetConsoleKey ('m', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('p', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('q', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('r', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('s', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('t', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Clear, EscSeqUtils.GetConsoleKey ('u', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('v', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('w', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('x', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('y', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Add, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('l', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Subtract, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('m', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('p', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('q', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('r', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('s', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('t', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Clear, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('u', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.RightArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('v', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('w', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.UpArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('x', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('y', null, ref mod, ref keyChar)); } [Fact] public void GetConsoleModifiers_Tests () { - Assert.Equal (ConsoleModifiers.Shift, EscSeqUtils.GetConsoleModifiers ("2")); - Assert.Equal (ConsoleModifiers.Alt, EscSeqUtils.GetConsoleModifiers ("3")); - Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, EscSeqUtils.GetConsoleModifiers ("4")); - Assert.Equal (ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("5")); - Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("6")); - Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("7")); + Assert.Equal (ConsoleModifiers.Shift, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("2")); + Assert.Equal (ConsoleModifiers.Alt, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("3")); + Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("4")); + Assert.Equal (ConsoleModifiers.Control, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("5")); + Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("6")); + Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("7")); Assert.Equal ( ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, - EscSeqUtils.GetConsoleModifiers ("8") + AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("8") ); - Assert.Equal (0, (int)EscSeqUtils.GetConsoleModifiers ("")); + Assert.Equal (0, (int)AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("")); } [Fact] public void GetEscapeResult_Multiple_Tests () { char [] kChars = ['\u001b', '[', '5', ';', '1', '0', 'r']; - (_c1Control, _code, _values, _terminating) = EscSeqUtils.GetEscapeResult (kChars); + (_c1Control, _code, _values, _terminating) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (kChars); Assert.Equal ("CSI", _c1Control); Assert.Null (_code); Assert.Equal (2, _values.Length); @@ -1307,7 +1307,7 @@ public class EscSeqUtilsTests [InlineData (['A'])] public void GetEscapeResult_Single_Tests (params char [] kChars) { - (_c1Control, _code, _values, _terminating) = EscSeqUtils.GetEscapeResult (kChars); + (_c1Control, _code, _values, _terminating) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (kChars); if (kChars [0] == '\u001B') { @@ -1337,7 +1337,7 @@ public class EscSeqUtilsTests new ('r', 0, false, false, false) }; - Assert.Equal (new [] { '\u001b', '[', '5', ';', '1', '0', 'r' }, EscSeqUtils.GetKeyCharArray (cki)); + Assert.Equal (new [] { '\u001b', '[', '5', ';', '1', '0', 'r' }, AnsiEscapeSequenceRequestUtils.GetKeyCharArray (cki)); } [Fact] @@ -1356,7 +1356,7 @@ public class EscSeqUtilsTests new ('3', 0, false, false, false), new ('M', 0, false, false, false) }; - EscSeqUtils.GetMouse (cki, out List mouseFlags, out Point pos, ProcessContinuousButtonPressed); + AnsiEscapeSequenceRequestUtils.GetMouse (cki, out List mouseFlags, out Point pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1Pressed }, mouseFlags); Assert.Equal (new (1, 2), pos); @@ -1372,7 +1372,7 @@ public class EscSeqUtilsTests new ('3', 0, false, false, false), new ('m', 0, false, false, false) }; - EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (2, mouseFlags.Count); Assert.Equal ( @@ -1393,7 +1393,7 @@ public class EscSeqUtilsTests new ('3', 0, false, false, false), new ('M', 0, false, false, false) }; - EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1DoubleClicked }, mouseFlags); Assert.Equal (new (1, 2), pos); @@ -1409,7 +1409,7 @@ public class EscSeqUtilsTests new ('3', 0, false, false, false), new ('M', 0, false, false, false) }; - EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1TripleClicked }, mouseFlags); Assert.Equal (new (1, 2), pos); @@ -1425,7 +1425,7 @@ public class EscSeqUtilsTests new ('3', 0, false, false, false), new ('m', 0, false, false, false) }; - EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1Released }, mouseFlags); Assert.Equal (new (1, 2), pos); } @@ -1435,7 +1435,7 @@ public class EscSeqUtilsTests { ConsoleKeyInfo [] expectedCkInfos = null; var cki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false); - expectedCkInfos = EscSeqUtils.ResizeArray (cki, expectedCkInfos); + expectedCkInfos = AnsiEscapeSequenceRequestUtils.ResizeArray (cki, expectedCkInfos); Assert.Single (expectedCkInfos); Assert.Equal (cki, expectedCkInfos [0]); } @@ -1494,7 +1494,7 @@ public class EscSeqUtilsTests [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC_", "_")] public void SplitEscapeRawString_Multiple_Tests (string rawData, string expectedLast) { - List splitList = EscSeqUtils.SplitEscapeRawString (rawData); + List splitList = AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (rawData); Assert.Equal (18, splitList.Count); Assert.Equal ("\r", splitList [0]); Assert.Equal ("\u001b[<35;50;1m", splitList [1]); @@ -1525,7 +1525,7 @@ public class EscSeqUtilsTests [InlineData ("A")] public void SplitEscapeRawString_Single_Tests (string rawData) { - List splitList = EscSeqUtils.SplitEscapeRawString (rawData); + List splitList = AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (rawData); Assert.Single (splitList); Assert.Equal (rawData, splitList [0]); } @@ -1541,23 +1541,23 @@ public class EscSeqUtilsTests [InlineData ("0;20t", "\u001b[8;1", 3, "\u001b[80;20t;1")] public void InsertArray_Tests (string toInsert, string current, int? index, string expected) { - ConsoleKeyInfo [] toIns = EscSeqUtils.ToConsoleKeyInfoArray (toInsert); - ConsoleKeyInfo [] cki = EscSeqUtils.ToConsoleKeyInfoArray (current); - ConsoleKeyInfo [] result = EscSeqUtils.ToConsoleKeyInfoArray (expected); + ConsoleKeyInfo [] toIns = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (toInsert); + ConsoleKeyInfo [] cki = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (current); + ConsoleKeyInfo [] result = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (expected); if (index is null) { - Assert.Equal (result, EscSeqUtils.InsertArray (toIns, cki)); + Assert.Equal (result, AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki)); } else { - Assert.Equal (result, EscSeqUtils.InsertArray (toIns, cki, (int)index)); + Assert.Equal (result, AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki, (int)index)); } } private void ClearAll () { - _escSeqReqProc = default (EscSeqRequests); + _escSeqReqProc = default (AnsiEscapeSequenceRequests); _newConsoleKeyInfo = default (ConsoleKeyInfo); _key = default (ConsoleKey); _cki = default (ConsoleKeyInfo []); diff --git a/UnitTests/Input/EscSeqReqTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs similarity index 82% rename from UnitTests/Input/EscSeqReqTests.cs rename to UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs index 9f6921796..0184ab122 100644 --- a/UnitTests/Input/EscSeqReqTests.cs +++ b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs @@ -1,11 +1,11 @@ namespace Terminal.Gui.InputTests; -public class EscSeqReqTests +public class AnsiEscapeSequenceRequestsTests { [Fact] public void Add_Tests () { - var escSeqReq = new EscSeqRequests (); + var escSeqReq = new AnsiEscapeSequenceRequests (); escSeqReq.Add (new () { Request = "", Terminator = "t" }); Assert.Single (escSeqReq.Statuses); Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); @@ -28,7 +28,7 @@ public class EscSeqReqTests [Fact] public void Constructor_Defaults () { - var escSeqReq = new EscSeqRequests (); + var escSeqReq = new AnsiEscapeSequenceRequests (); Assert.NotNull (escSeqReq.Statuses); Assert.Empty (escSeqReq.Statuses); } @@ -36,9 +36,9 @@ public class EscSeqReqTests [Fact] public void Remove_Tests () { - var escSeqReq = new EscSeqRequests (); + var escSeqReq = new AnsiEscapeSequenceRequests (); escSeqReq.Add (new () { Request = "", Terminator = "t" }); - escSeqReq.HasResponse ("t", out EscSeqReqStatus seqReqStatus); + escSeqReq.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus); escSeqReq.Remove (seqReqStatus); Assert.Empty (escSeqReq.Statuses); @@ -57,8 +57,8 @@ public class EscSeqReqTests [Fact] public void Requested_Tests () { - var escSeqReq = new EscSeqRequests (); - Assert.False (escSeqReq.HasResponse ("t", out EscSeqReqStatus seqReqStatus)); + var escSeqReq = new AnsiEscapeSequenceRequests (); + Assert.False (escSeqReq.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus)); Assert.Null (seqReqStatus); escSeqReq.Add (new () { Request = "", Terminator = "t" }); From dba089fda01dc0bc0ff42daa1a37208981e716c7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 00:42:22 +0000 Subject: [PATCH 082/151] Change category. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index af8b0d868..e7485da62 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -6,7 +6,7 @@ using Terminal.Gui; namespace UICatalog.Scenarios; [ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")] -[ScenarioCategory ("Ansi Escape Sequence")] +[ScenarioCategory ("Tests")] public sealed class AnsiEscapeSequenceRequests : Scenario { private GraphView _graphView; From 5efba6a5bc9e05679f5cc7b1f734cf9a1a6ed3e7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 00:44:02 +0000 Subject: [PATCH 083/151] Code cleanup. --- .../Scenarios/AnsiEscapeSequenceRequests.cs | 283 +++++++++--------- 1 file changed, 141 insertions(+), 142 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index e7485da62..6fbc445c4 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -9,15 +9,14 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Tests")] public sealed class AnsiEscapeSequenceRequests : Scenario { - private GraphView _graphView; - - private ScatterSeries _sentSeries; - private ScatterSeries _answeredSeries; - private readonly List _sends = new (); private readonly object _lockAnswers = new (); private readonly Dictionary _answers = new (); + private GraphView _graphView; + + private ScatterSeries _sentSeries; + private ScatterSeries _answeredSeries; private Label _lblSummary; public override void Main () @@ -60,6 +59,120 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Application.Shutdown (); } + private View BuildBulkTab () + { + var w = new View + { + Width = Dim.Fill (), + Height = Dim.Fill (), + CanFocus = true + }; + + var lbl = new Label + { + Text = + "_This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.", + Height = 2, + Width = Dim.Fill () + }; + + Application.AddTimeout ( + TimeSpan.FromMilliseconds (1000), + () => + { + lock (_lockAnswers) + { + UpdateGraph (); + + UpdateResponses (); + } + + return true; + }); + + var tv = new TextView + { + Y = Pos.Bottom (lbl), + Width = Dim.Percent (50), + Height = Dim.Fill () + }; + + var lblDar = new Label + { + Y = Pos.Bottom (lbl), + X = Pos.Right (tv) + 1, + Text = "_DAR per second: " + }; + + var cbDar = new NumericUpDown + { + X = Pos.Right (lblDar), + Y = Pos.Bottom (lbl), + Value = 0 + }; + + cbDar.ValueChanging += (s, e) => + { + if (e.NewValue < 0 || e.NewValue > 20) + { + e.Cancel = true; + } + }; + w.Add (cbDar); + + int lastSendTime = Environment.TickCount; + var lockObj = new object (); + + Application.AddTimeout ( + TimeSpan.FromMilliseconds (50), + () => + { + lock (lockObj) + { + if (cbDar.Value > 0) + { + int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds + int currentTime = Environment.TickCount; // Current system time in milliseconds + + // Check if the time elapsed since the last send is greater than the interval + if (currentTime - lastSendTime >= interval) + { + SendDar (); // Send the request + lastSendTime = currentTime; // Update the last send time + } + } + } + + return true; + }); + + _graphView = new () + { + Y = Pos.Bottom (cbDar), + X = Pos.Right (tv), + Width = Dim.Fill (), + Height = Dim.Fill (1) + }; + + _lblSummary = new () + { + Y = Pos.Bottom (_graphView), + X = Pos.Right (tv), + Width = Dim.Fill () + }; + + SetupGraph (); + + w.Add (lbl); + w.Add (lblDar); + w.Add (cbDar); + w.Add (tv); + w.Add (_graphView); + w.Add (_lblSummary); + + return w; + } + private View BuildSingleTab () { var w = new View @@ -195,126 +308,6 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return w; } - private View BuildBulkTab () - { - var w = new View - { - Width = Dim.Fill (), - Height = Dim.Fill (), - CanFocus = true - }; - - var lbl = new Label - { - Text = - "_This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.", - Height = 2, - Width = Dim.Fill () - }; - - Application.AddTimeout ( - TimeSpan.FromMilliseconds (1000), - () => - { - lock (_lockAnswers) - { - UpdateGraph (); - - UpdateResponses (); - } - - return true; - }); - - var tv = new TextView - { - Y = Pos.Bottom (lbl), - Width = Dim.Percent (50), - Height = Dim.Fill () - }; - - var lblDar = new Label - { - Y = Pos.Bottom (lbl), - X = Pos.Right (tv) + 1, - Text = "_DAR per second: " - }; - - var cbDar = new NumericUpDown - { - X = Pos.Right (lblDar), - Y = Pos.Bottom (lbl), - Value = 0 - }; - - cbDar.ValueChanging += (s, e) => - { - if (e.NewValue < 0 || e.NewValue > 20) - { - e.Cancel = true; - } - }; - w.Add (cbDar); - - int lastSendTime = Environment.TickCount; - var lockObj = new object (); - - Application.AddTimeout ( - TimeSpan.FromMilliseconds (50), - () => - { - lock (lockObj) - { - if (cbDar.Value > 0) - { - int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds - int currentTime = Environment.TickCount; // Current system time in milliseconds - - // Check if the time elapsed since the last send is greater than the interval - if (currentTime - lastSendTime >= interval) - { - SendDar (); // Send the request - lastSendTime = currentTime; // Update the last send time - } - } - } - - return true; - }); - - _graphView = new () - { - Y = Pos.Bottom (cbDar), - X = Pos.Right (tv), - Width = Dim.Fill (), - Height = Dim.Fill (1) - }; - - _lblSummary = new () - { - Y = Pos.Bottom (_graphView), - X = Pos.Right (tv), - Width = Dim.Fill () - }; - - SetupGraph (); - - w.Add (lbl); - w.Add (lblDar); - w.Add (cbDar); - w.Add (tv); - w.Add (_graphView); - w.Add (_lblSummary); - - return w; - } - - private void UpdateResponses () - { - _lblSummary.Text = GetSummary (); - _lblSummary.SetNeedsDisplay (); - } - private string GetSummary () { if (_answers.Count == 0) @@ -330,6 +323,21 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return $"Last:{last} U:{unique} T:{total}"; } + private void HandleResponse (string response) + { + lock (_lockAnswers) + { + _answers.Add (DateTime.Now, response); + } + } + + private void SendDar () + { + _sends.Add (DateTime.Now); + string result = Application.Driver.WriteAnsiRequest (AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes); + HandleResponse (result); + } + private void SetupGraph () { _graphView.Series.Add (_sentSeries = new ()); @@ -348,6 +356,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario _graphView.GraphColor = new Attribute (Color.Green, Color.Black); } + private int ToSeconds (DateTime t) { return (int)(DateTime.Now - t).TotalSeconds; } + private void UpdateGraph () { _sentSeries.Points = _sends @@ -356,9 +366,9 @@ public sealed class AnsiEscapeSequenceRequests : Scenario .ToList (); _answeredSeries.Points = _answers.Keys - .GroupBy (ToSeconds) - .Select (g => new PointF (g.Key, g.Count ())) - .ToList (); + .GroupBy (ToSeconds) + .Select (g => new PointF (g.Key, g.Count ())) + .ToList (); // _graphView.ScrollOffset = new PointF(,0); if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) @@ -367,20 +377,9 @@ public sealed class AnsiEscapeSequenceRequests : Scenario } } - private int ToSeconds (DateTime t) { return (int)(DateTime.Now - t).TotalSeconds; } - - private void SendDar () + private void UpdateResponses () { - _sends.Add (DateTime.Now); - string result = Application.Driver.WriteAnsiRequest (AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes); - HandleResponse (result); - } - - private void HandleResponse (string response) - { - lock (_lockAnswers) - { - _answers.Add (DateTime.Now, response); - } + _lblSummary.Text = GetSummary (); + _lblSummary.SetNeedsDisplay (); } } From b7b9e015026bf2b067f0fc0775628a305c009920 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 01:11:52 +0000 Subject: [PATCH 084/151] Fix error with Key.Space using lowercase letters. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index 844112d65..75a5937c6 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -1140,10 +1140,11 @@ public static class AnsiEscapeSequenceRequestUtils if (isConsoleKey) { key = (ConsoleKey)ck; + keyChar = (char)ck; } newConsoleKeyInfo = new ( - consoleKeyInfo.KeyChar, + keyChar, key, GetShiftMod (consoleKeyInfo.Modifiers), (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, From a64f68ca7f353770b2d0ccef1800576a85ccfc9c Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 02:00:44 +0000 Subject: [PATCH 085/151] Moving MapKey method to the AnsiEscapeSequenceRequestUtils class. --- .../AnsiEscapeSequenceRequestUtils.cs | 104 +++++++++++++++++- .../CursesDriver/CursesDriver.cs | 2 +- .../ConsoleDrivers/NetDriver/NetDriver.cs | 102 +---------------- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index 75a5937c6..0cef28e80 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -1,5 +1,6 @@ #nullable enable using Terminal.Gui.ConsoleDrivers; +using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; namespace Terminal.Gui; @@ -1135,12 +1136,11 @@ public static class AnsiEscapeSequenceRequestUtils break; default: - uint ck = ConsoleKeyMapping.MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey); + uint ck = MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey); if (isConsoleKey) { key = (ConsoleKey)ck; - keyChar = (char)ck; } newConsoleKeyInfo = new ( @@ -1363,6 +1363,106 @@ public static class AnsiEscapeSequenceRequestUtils return mf; } + internal static KeyCode MapKey (ConsoleKeyInfo keyInfo) + { + switch (keyInfo.Key) + { + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + case ConsoleKey.Packet: + case ConsoleKey.Oem1: + case ConsoleKey.Oem2: + case ConsoleKey.Oem3: + case ConsoleKey.Oem4: + case ConsoleKey.Oem5: + case ConsoleKey.Oem6: + case ConsoleKey.Oem7: + case ConsoleKey.Oem8: + case ConsoleKey.Oem102: + if (keyInfo.KeyChar == 0) + { + // If the keyChar is 0, keyInfo.Key value is not a printable character. + + return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); + } + + if (keyInfo.Modifiers != ConsoleModifiers.Shift) + { + // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); + } + + // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç") + // and passing on Shift would be redundant. + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); + } + + // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC + if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) + { + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I) + { + return KeyCode.Tab; + } + + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); + } + + // Handle control keys (e.g. CursorUp) + if (keyInfo.Key != ConsoleKey.None + && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)) + { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); + } + + if ((ConsoleKey)keyInfo.KeyChar is >= ConsoleKey.A and <= ConsoleKey.Z) + { + // Shifted + keyInfo = new ( + keyInfo.KeyChar, + (ConsoleKey)keyInfo.KeyChar, + true, + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); + } + + if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z) + { + // Unshifted + keyInfo = new ( + keyInfo.KeyChar, + (ConsoleKey)(keyInfo.KeyChar - 32), + false, + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), + keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); + } + + if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) + { + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) + || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) + { + // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key); + } + + if (keyInfo.Modifiers == ConsoleModifiers.Shift) + { + // If ShiftMask is on add the ShiftMask + if (char.IsUpper (keyInfo.KeyChar)) + { + return (KeyCode)keyInfo.Key | KeyCode.ShiftMask; + } + } + + return (KeyCode)keyInfo.Key; + } + + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); + } + private static async Task ProcessButtonClickedAsync () { await Task.Delay (300); diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 80b74edf6..866bfa496 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -783,7 +783,7 @@ internal class CursesDriver : ConsoleDriver case UnixMainLoop.EventType.Key: ConsoleKeyInfo consoleKeyInfo = inputEvent.KeyEvent; - KeyCode map = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); + KeyCode map = AnsiEscapeSequenceRequestUtils.MapKey (consoleKeyInfo); if (map == KeyCode.Null) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index d650c91f0..69fb78253 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -315,7 +315,7 @@ internal class NetDriver : ConsoleDriver //Debug.WriteLine ($"event: {inputEvent}"); - KeyCode map = MapKey (consoleKeyInfo); + KeyCode map = AnsiEscapeSequenceRequestUtils.MapKey (consoleKeyInfo); if (map == KeyCode.Null) { @@ -722,106 +722,6 @@ internal class NetDriver : ConsoleDriver return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); } - private KeyCode MapKey (ConsoleKeyInfo keyInfo) - { - switch (keyInfo.Key) - { - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: - case ConsoleKey.Packet: - case ConsoleKey.Oem1: - case ConsoleKey.Oem2: - case ConsoleKey.Oem3: - case ConsoleKey.Oem4: - case ConsoleKey.Oem5: - case ConsoleKey.Oem6: - case ConsoleKey.Oem7: - case ConsoleKey.Oem8: - case ConsoleKey.Oem102: - if (keyInfo.KeyChar == 0) - { - // If the keyChar is 0, keyInfo.Key value is not a printable character. - - return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); - } - - if (keyInfo.Modifiers != ConsoleModifiers.Shift) - { - // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); - } - - // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç") - // and passing on Shift would be redundant. - return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); - } - - // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC - if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) - { - if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I) - { - return KeyCode.Tab; - } - - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); - } - - // Handle control keys (e.g. CursorUp) - if (keyInfo.Key != ConsoleKey.None - && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)) - { - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); - } - - if ((ConsoleKey)keyInfo.KeyChar is >= ConsoleKey.A and <= ConsoleKey.Z) - { - // Shifted - keyInfo = new ( - keyInfo.KeyChar, - (ConsoleKey)keyInfo.KeyChar, - true, - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); - } - - if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z) - { - // Unshifted - keyInfo = new ( - keyInfo.KeyChar, - (ConsoleKey)(keyInfo.KeyChar - 32), - false, - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), - keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); - } - - if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) - { - if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) - || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) - { - // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos - return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key); - } - - if (keyInfo.Modifiers == ConsoleModifiers.Shift) - { - // If ShiftMask is on add the ShiftMask - if (char.IsUpper (keyInfo.KeyChar)) - { - return (KeyCode)keyInfo.Key | KeyCode.ShiftMask; - } - } - - return (KeyCode)keyInfo.Key; - } - - return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); - } - #endregion Keyboard Handling #region Low-Level DotNet tuff From cc21bd461dcfe8f31bb2f6c63e07380b23753dc3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 13:01:03 +0000 Subject: [PATCH 086/151] Fix a bug where a esc key was ignored. --- .../AnsiEscapeSequenceRequestUtils.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index 0cef28e80..ca8533f77 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -1078,7 +1078,18 @@ public static class AnsiEscapeSequenceRequestUtils break; case uint n when n is > 0 and <= KeyEsc: - if (consoleKeyInfo is { Key: 0, KeyChar: '\t' }) + if (consoleKeyInfo is { Key: 0, KeyChar: '\u001B' }) + { + key = ConsoleKey.Escape; + + newConsoleKeyInfo = new ( + consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + } + else if (consoleKeyInfo is { Key: 0, KeyChar: '\t' }) { key = ConsoleKey.Tab; From 3e952df5f7b3341f31b3f862ead1712ea270f20a Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 13:04:11 +0000 Subject: [PATCH 087/151] Change to BlockingCollection, thanks to @tznind. --- .../ConsoleDrivers/NetDriver/NetDriver.cs | 12 +- .../ConsoleDrivers/NetDriver/NetEvents.cs | 274 ++++++++---------- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 76 ++--- 3 files changed, 142 insertions(+), 220 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 69fb78253..0422f548f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -754,17 +754,7 @@ internal class NetDriver : ConsoleDriver _mainLoopDriver._netEvents._forceRead = true; } - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - _mainLoopDriver._netEvents._waitForStart.Set (); - - if (!_mainLoopDriver._waitForProbe.IsSet) - { - _mainLoopDriver._waitForProbe.Set (); - } - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); } catch (OperationCanceledException) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 3e5414241..9976c0136 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -6,13 +6,8 @@ namespace Terminal.Gui; internal class NetEvents : IDisposable { - private readonly ManualResetEventSlim _inputReady = new (false); private CancellationTokenSource _inputReadyCancellationTokenSource; - internal readonly ManualResetEventSlim _waitForStart = new (false); - - //CancellationTokenSource _waitForStartCancellationTokenSource; - private readonly ManualResetEventSlim _winChange = new (false); - private readonly ConcurrentQueue _inputQueue = new (); + private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); private readonly ConsoleDriver _consoleDriver; private ConsoleKeyInfo [] _cki; private bool _isEscSeq; @@ -33,41 +28,20 @@ internal class NetEvents : IDisposable public InputResult? DequeueInput () { - while (_inputReadyCancellationTokenSource != null - && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested) + while (_inputReadyCancellationTokenSource is { }) { - _waitForStart.Set (); - _winChange.Set (); - try { - if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) - { - if (_inputQueue.Count == 0) - { - _inputReady.Wait (_inputReadyCancellationTokenSource.Token); - } - } + return _inputQueue.Take (_inputReadyCancellationTokenSource.Token); } catch (OperationCanceledException) { return null; } - finally - { - _inputReady.Reset (); - } #if PROCESS_REQUEST _neededProcessRequest = false; #endif - if (_inputQueue.Count > 0) - { - if (_inputQueue.TryDequeue (out InputResult? result)) - { - return result; - } - } } return null; @@ -130,129 +104,120 @@ internal class NetEvents : IDisposable { try { - if (!_forceRead) + if (_inputQueue.Count == 0 || _forceRead) { - _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); + ConsoleKey key = 0; + ConsoleModifiers mod = 0; + ConsoleKeyInfo newConsoleKeyInfo = default; + + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + ConsoleKeyInfo consoleKeyInfo; + + try + { + consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) + { + AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); + } + + if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) + || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) + { + if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( + new ( + (char)KeyCode.Esc, + 0, + false, + false, + false + ), + _cki + ); + } + + _isEscSeq = true; + + if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) + || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))) + { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + else + { + newConsoleKeyInfo = consoleKeyInfo; + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + + if (Console.KeyAvailable) + { + continue; + } + + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + } + + break; + } + + if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) + { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + + if (Console.KeyAvailable) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + } + else + { + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + + break; + } + + ProcessMapConsoleKeyInfo (consoleKeyInfo); + + if (_retries > 0) + { + _retries = 0; + } + + break; + } } } catch (OperationCanceledException) { return; } - - _waitForStart.Reset (); - - if (_inputQueue.Count == 0 || _forceRead) - { - ConsoleKey key = 0; - ConsoleModifiers mod = 0; - ConsoleKeyInfo newConsoleKeyInfo = default; - - while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) - { - ConsoleKeyInfo consoleKeyInfo; - - try - { - consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) - { - AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); - } - - if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) - || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) - { - if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) - { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( - new ConsoleKeyInfo ( - (char)KeyCode.Esc, - 0, - false, - false, - false - ), - _cki - ); - } - - _isEscSeq = true; - - if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) - || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) - || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))) - { - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - } - else - { - newConsoleKeyInfo = consoleKeyInfo; - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - - if (Console.KeyAvailable) - { - continue; - } - - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - } - - break; - } - - if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) - { - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - - if (Console.KeyAvailable) - { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - } - else - { - ProcessMapConsoleKeyInfo (consoleKeyInfo); - } - - break; - } - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - - if (_retries > 0) - { - _retries = 0; - } - - break; - } - } - - _inputReady.Set (); } void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) { - _inputQueue.Enqueue ( - new InputResult - { - EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo) - } - ); + _inputQueue.Add ( + new () + { + EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo) + } + ); _isEscSeq = false; } } @@ -297,17 +262,12 @@ internal class NetEvents : IDisposable { try { - _winChange.Wait (_inputReadyCancellationTokenSource.Token); - _winChange.Reset (); - RequestWindowSize (_inputReadyCancellationTokenSource.Token); } catch (OperationCanceledException) { return; } - - _inputReady.Set (); } } @@ -327,12 +287,12 @@ internal class NetEvents : IDisposable int w = Math.Max (winWidth, 0); int h = Math.Max (winHeight, 0); - _inputQueue.Enqueue ( - new InputResult - { - EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) } - } - ); + _inputQueue.Add ( + new () + { + EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (w, h) } + } + ); return true; } @@ -609,11 +569,9 @@ internal class NetEvents : IDisposable { var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState }; - _inputQueue.Enqueue ( - new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent } - ); - - _inputReady.Set (); + _inputQueue.Add ( + new () { EventType = EventType.Mouse, MouseEvent = mouseEvent } + ); } public enum EventType @@ -726,7 +684,7 @@ internal class NetEvents : IDisposable { var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki }; - _inputQueue.Enqueue (inputResult); + _inputQueue.Add (inputResult); } public void Dispose () diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index d876ef0cb..6e68bf8d5 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -16,8 +16,7 @@ internal class NetMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - private readonly ConcurrentQueue _resultQueue = new (); - internal readonly ManualResetEventSlim _waitForProbe = new (false); + private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ()); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private MainLoop _mainLoop; @@ -27,12 +26,9 @@ internal class NetMainLoop : IMainLoopDriver /// public NetMainLoop (ConsoleDriver consoleDriver = null) { - if (consoleDriver is null) - { - throw new ArgumentNullException (nameof (consoleDriver)); - } + ArgumentNullException.ThrowIfNull (consoleDriver); - _netEvents = new NetEvents (consoleDriver); + _netEvents = new (consoleDriver); } void IMainLoopDriver.Setup (MainLoop mainLoop) @@ -51,9 +47,7 @@ internal class NetMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForProbe.Set (); - - if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (_resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; } @@ -83,6 +77,7 @@ internal class NetMainLoop : IMainLoopDriver return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); } + // If cancellation was requested then always return true return true; } @@ -91,11 +86,11 @@ internal class NetMainLoop : IMainLoopDriver while (_resultQueue.Count > 0) { // Always dequeue even if it's null and invoke if isn't null - if (_resultQueue.TryDequeue (out NetEvents.InputResult? dequeueResult)) + if (_resultQueue.TryTake (out NetEvents.InputResult dequeueResult)) { if (dequeueResult is { }) { - ProcessInput?.Invoke (dequeueResult.Value); + ProcessInput?.Invoke (dequeueResult); } } } @@ -110,8 +105,7 @@ internal class NetMainLoop : IMainLoopDriver _eventReady?.Dispose (); - _resultQueue?.Clear (); - _waitForProbe?.Dispose (); + _resultQueue?.Dispose(); _netEvents?.Dispose (); _netEvents = null; @@ -124,50 +118,30 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested) + if (_inputHandlerTokenSource.IsCancellationRequested) { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); + return; + } + + if (_resultQueue?.Count == 0 || _netEvents._forceRead) + { + NetEvents.InputResult? result = _netEvents.DequeueInput (); + + if (result.HasValue) + { + _resultQueue?.Add (result.Value); + } + } + + if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0) + { + _eventReady.Set (); } } catch (OperationCanceledException) { return; } - finally - { - if (_waitForProbe.IsSet) - { - _waitForProbe.Reset (); - } - } - - if (_inputHandlerTokenSource.IsCancellationRequested) - { - return; - } - - _inputHandlerTokenSource.Token.ThrowIfCancellationRequested (); - - if (_resultQueue.Count == 0) - { - _resultQueue.Enqueue (_netEvents.DequeueInput ()); - } - - try - { - while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out NetEvents.InputResult? result) && result is null) - { - // Dequeue null values - _resultQueue.TryDequeue (out _); - } - } - catch (InvalidOperationException) // Peek can raise an exception - { } - - if (_resultQueue.Count > 0) - { - _eventReady.Set (); - } } } } From 44d59974606098575bf039642f2eabe28f6e5f37 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 14:46:27 +0000 Subject: [PATCH 088/151] Split more WindowDriver classes and #nullable enable. --- .../WindowsDriver/ClipboardImpl.cs | 179 +++++++ .../WindowsDriver/WindowsConsole.cs | 56 +- .../WindowsDriver/WindowsDriver.cs | 492 ++---------------- .../WindowsDriver/WindowsMainLoop.cs | 248 +++++++++ 4 files changed, 492 insertions(+), 483 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/WindowsDriver/ClipboardImpl.cs create mode 100644 Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/ClipboardImpl.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/ClipboardImpl.cs new file mode 100644 index 000000000..d38ffb408 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/ClipboardImpl.cs @@ -0,0 +1,179 @@ +#nullable enable +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Terminal.Gui; + +internal class WindowsClipboard : ClipboardBase +{ + private const uint CF_UNICODE_TEXT = 13; + + public override bool IsSupported { get; } = CheckClipboardIsAvailable (); + + private static bool CheckClipboardIsAvailable () + { + // Attempt to open the clipboard + if (OpenClipboard (nint.Zero)) + { + // Clipboard is available + // Close the clipboard after use + CloseClipboard (); + + return true; + } + // Clipboard is not available + return false; + } + + protected override string GetClipboardDataImpl () + { + try + { + if (!OpenClipboard (nint.Zero)) + { + return string.Empty; + } + + nint handle = GetClipboardData (CF_UNICODE_TEXT); + + if (handle == nint.Zero) + { + return string.Empty; + } + + nint pointer = nint.Zero; + + try + { + pointer = GlobalLock (handle); + + if (pointer == nint.Zero) + { + return string.Empty; + } + + int size = GlobalSize (handle); + var buff = new byte [size]; + + Marshal.Copy (pointer, buff, 0, size); + + return Encoding.Unicode.GetString (buff).TrimEnd ('\0'); + } + finally + { + if (pointer != nint.Zero) + { + GlobalUnlock (handle); + } + } + } + finally + { + CloseClipboard (); + } + } + + protected override void SetClipboardDataImpl (string text) + { + OpenClipboard (); + + EmptyClipboard (); + nint hGlobal = default; + + try + { + int bytes = (text.Length + 1) * 2; + hGlobal = Marshal.AllocHGlobal (bytes); + + if (hGlobal == default (nint)) + { + ThrowWin32 (); + } + + nint target = GlobalLock (hGlobal); + + if (target == default (nint)) + { + ThrowWin32 (); + } + + try + { + Marshal.Copy (text.ToCharArray (), 0, target, text.Length); + } + finally + { + GlobalUnlock (target); + } + + if (SetClipboardData (CF_UNICODE_TEXT, hGlobal) == default (nint)) + { + ThrowWin32 (); + } + + hGlobal = default (nint); + } + finally + { + if (hGlobal != default (nint)) + { + Marshal.FreeHGlobal (hGlobal); + } + + CloseClipboard (); + } + } + + [DllImport ("user32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static extern bool CloseClipboard (); + + [DllImport ("user32.dll")] + private static extern bool EmptyClipboard (); + + [DllImport ("user32.dll", SetLastError = true)] + private static extern nint GetClipboardData (uint uFormat); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern nint GlobalLock (nint hMem); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern int GlobalSize (nint handle); + + [DllImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static extern bool GlobalUnlock (nint hMem); + + [DllImport ("User32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static extern bool IsClipboardFormatAvailable (uint format); + + private void OpenClipboard () + { + var num = 10; + + while (true) + { + if (OpenClipboard (default (nint))) + { + break; + } + + if (--num == 0) + { + ThrowWin32 (); + } + + Thread.Sleep (100); + } + } + + [DllImport ("user32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static extern bool OpenClipboard (nint hWndNewOwner); + + [DllImport ("user32.dll", SetLastError = true)] + private static extern nint SetClipboardData (uint uFormat, nint data); + + private void ThrowWin32 () { throw new Win32Exception (Marshal.GetLastWin32Error ()); } +} diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 430085a2a..ff8d155b7 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -1,4 +1,4 @@ -// TODO: #nullable enable +#nullable enable using System.ComponentModel; using System.Runtime.InteropServices; using Terminal.Gui.ConsoleDrivers; @@ -7,7 +7,7 @@ namespace Terminal.Gui; internal class WindowsConsole { - internal WindowsMainLoop _mainLoop; + internal WindowsMainLoop? _mainLoop; public const int STD_OUTPUT_HANDLE = -11; public const int STD_INPUT_HANDLE = -10; @@ -34,7 +34,7 @@ internal class WindowsConsole ConsoleMode = newConsoleMode; } - private CharInfo [] _originalStdOutChars; + private CharInfo []? _originalStdOutChars; public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) { @@ -598,15 +598,15 @@ internal class WindowsConsole public readonly override string ToString () { - return EventType switch - { - EventType.Focus => FocusEvent.ToString (), - EventType.Key => KeyEvent.ToString (), - EventType.Menu => MenuEvent.ToString (), - EventType.Mouse => MouseEvent.ToString (), - EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), - _ => "Unknown event type: " + EventType - }; + return (EventType switch + { + EventType.Focus => FocusEvent.ToString (), + EventType.Key => KeyEvent.ToString (), + EventType.Menu => MenuEvent.ToString (), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + })!; } } @@ -866,7 +866,7 @@ internal class WindowsConsole internal static nint INVALID_HANDLE_VALUE = new (-1); [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleActiveScreenBuffer (nint Handle); + private static extern bool SetConsoleActiveScreenBuffer (nint handle); [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents); @@ -896,9 +896,9 @@ internal class WindowsConsole private int _retries; - public InputRecord [] ReadConsoleInput () + public InputRecord []? ReadConsoleInput () { - const int bufferSize = 1; + const int BUFFER_SIZE = 1; InputRecord inputRecord = default; uint numberEventsRead = 0; StringBuilder ansiSequence = new StringBuilder (); @@ -910,13 +910,13 @@ internal class WindowsConsole try { // Peek to check if there is any input available - if (PeekConsoleInput (_inputHandle, out _, bufferSize, out uint eventsRead) && eventsRead > 0) + if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0) { // Read the input since it is available ReadConsoleInput ( _inputHandle, out inputRecord, - bufferSize, + BUFFER_SIZE, out numberEventsRead); if (inputRecord.EventType == EventType.Key) @@ -931,7 +931,7 @@ internal class WindowsConsole if (inputChar == '\u001B') // Escape character { // Peek to check if there is any input available with key event and bKeyDown - if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, bufferSize, out eventsRead) && eventsRead > 0) + if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0) { if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) { @@ -949,7 +949,7 @@ internal class WindowsConsole ansiSequence.Append (inputChar); // Check if the sequence has ended with an expected command terminator - if (_mainLoop.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus seqReqStatus)) + if (_mainLoop?.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus)) { // Finished reading the sequence and remove the enqueued request _mainLoop.EscSeqRequests.Remove (seqReqStatus); @@ -970,9 +970,9 @@ internal class WindowsConsole } } - if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus seqReqStatus); + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus); lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -984,13 +984,13 @@ internal class WindowsConsole _retries = 0; } - else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 }) + else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) { if (_retries > 1) { - if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { - lock (seqReqStatus!.AnsiRequest._responseLock) + lock (seqReqStatus.AnsiRequest._responseLock) { _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); @@ -1012,9 +1012,9 @@ internal class WindowsConsole _retries = 0; } - return numberEventsRead == 0 - ? null - : [inputRecord]; + return (numberEventsRead == 0 + ? null + : [inputRecord])!; } catch (Exception) { @@ -1096,7 +1096,7 @@ internal class WindowsConsole private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi); [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo); + private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo); [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool SetConsoleWindowInfo ( diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 9372fcb04..616dc9fb0 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -1,4 +1,4 @@ -// TODO: #nullable enable +#nullable enable // // WindowsDriver.cs: Windows specific driver // @@ -9,14 +9,13 @@ // 2) The values provided during Init (and the first WindowsConsole.EventType.WindowBufferSize) are not correct. // // If HACK_CHECK_WINCHANGED is defined then we ignore WindowsConsole.EventType.WindowBufferSize events -// and instead check the console size every 500ms in a thread in WidowsMainLoop. -// As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using +// and instead check the console size every 500ms in a thread in WidowsMainLoop. +// As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using // the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is // still incorrect so we still need this hack. //#define HACK_CHECK_WINCHANGED -using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; @@ -35,7 +34,7 @@ internal class WindowsDriver : ConsoleDriver private bool _isOneFingerDoubleClicked; private WindowsConsole.ButtonState? _lastMouseButtonPressed; - private WindowsMainLoop _mainLoopDriver; + private WindowsMainLoop? _mainLoopDriver; private WindowsConsole.ExtendedCharInfo [] _outputBuffer; private Point? _point; private Point _pointMove; @@ -45,7 +44,7 @@ internal class WindowsDriver : ConsoleDriver { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - WinConsole = new WindowsConsole (); + WinConsole = new (); // otherwise we're probably running in unit tests Clipboard = new WindowsClipboard (); @@ -68,7 +67,7 @@ internal class WindowsDriver : ConsoleDriver public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal); - public WindowsConsole WinConsole { get; private set; } + public WindowsConsole? WinConsole { get; private set; } public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) { @@ -202,7 +201,7 @@ internal class WindowsDriver : ConsoleDriver private readonly CancellationTokenSource _ansiResponseTokenSource = new (); /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { if (_mainLoopDriver is null) { @@ -242,12 +241,12 @@ internal class WindowsDriver : ConsoleDriver { _mainLoopDriver._forceRead = false; - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request)) + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) { - lock (request!.AnsiRequest._responseLock) + lock (request.AnsiRequest._responseLock) { // Bad request or no response at all _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); @@ -404,7 +403,7 @@ internal class WindowsDriver : ConsoleDriver for (var row = 0; row < Rows; row++) { - if (!_dirtyLines [row]) + if (!_dirtyLines! [row]) { continue; } @@ -414,7 +413,7 @@ internal class WindowsDriver : ConsoleDriver for (var col = 0; col < Cols; col++) { int position = row * Cols + col; - _outputBuffer [position].Attribute = Contents [row, col].Attribute.GetValueOrDefault (); + _outputBuffer [position].Attribute = Contents! [row, col].Attribute.GetValueOrDefault (); if (Contents [row, col].IsDirty == false) { @@ -504,7 +503,7 @@ internal class WindowsDriver : ConsoleDriver { // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED - Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); + Size winSize = WinConsole.GetConsoleOutputWindow (out Point _); Cols = winSize.Width; Rows = winSize.Height; } @@ -592,7 +591,7 @@ internal class WindowsDriver : ConsoleDriver case WindowsConsole.EventType.Mouse: MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent); - if (me is null || me.Flags == MouseFlags.None) + if (me.Flags == MouseFlags.None) { break; } @@ -717,7 +716,7 @@ internal class WindowsDriver : ConsoleDriver if (mapResult == 0) { // There is no mapping - this should not happen - Debug.Assert (mapResult != 0, $@"Unable to map the virtual key code {keyInfo.Key}."); + Debug.Assert (true, $@"Unable to map the virtual key code {keyInfo.Key}."); return KeyCode.Null; } @@ -727,13 +726,13 @@ internal class WindowsDriver : ConsoleDriver if (keyInfo.KeyChar == 0) { - // If the keyChar is 0, keyInfo.Key value is not a printable character. + // If the keyChar is 0, keyInfo.Key value is not a printable character. - // Dead keys (diacritics) are indicated by setting the top bit of the return value. + // Dead keys (diacritics) are indicated by setting the top bit of the return value. if ((mapResult & 0x80000000) != 0) { // Dead key (e.g. Oem2 '~'/'^' on POR keyboard) - // Option 1: Throw it out. + // Option 1: Throw it out. // - Apps will never see the dead keys // - If user presses a key that can be combined with the dead key ('a'), the right thing happens (app will see '�'). // - NOTE: With Dead Keys, KeyDown != KeyUp. The KeyUp event will have just the base char ('a'). @@ -754,7 +753,7 @@ internal class WindowsDriver : ConsoleDriver if (keyInfo.Modifiers != 0) { // These Oem keys have well-defined chars. We ensure the representative char is used. - // If we don't do this, then on some keyboard layouts the wrong char is + // If we don't do this, then on some keyboard layouts the wrong char is // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important // for key persistence ("Ctrl++" vs. "Ctrl+="). mappedChar = keyInfo.Key switch @@ -925,25 +924,25 @@ internal class WindowsDriver : ConsoleDriver { // When a user presses-and-holds, start generating pressed events every `startDelay` // After `iterationsUntilFast` iterations, speed them up to `fastDelay` ms - const int startDelay = 500; - const int iterationsUntilFast = 4; - const int fastDelay = 50; + const int START_DELAY = 500; + const int ITERATIONS_UNTIL_FAST = 4; + const int FAST_DELAY = 50; int iterations = 0; - int delay = startDelay; + int delay = START_DELAY; while (_isButtonPressed) { // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - View view = Application.WantContinuousButtonPressedView; + View? view = Application.WantContinuousButtonPressedView; if (view is null) { break; } - if (iterations++ >= iterationsUntilFast) + if (iterations++ >= ITERATIONS_UNTIL_FAST) { - delay = fastDelay; + delay = FAST_DELAY; } await Task.Delay (delay); @@ -1012,13 +1011,13 @@ internal class WindowsDriver : ConsoleDriver if (_isButtonDoubleClicked || _isOneFingerDoubleClicked) { // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.MainLoop.AddIdle ( - () => - { - Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); + Application.MainLoop!.AddIdle ( + () => + { + Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); - return false; - }); + return false; + }); } // The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button. @@ -1084,13 +1083,13 @@ internal class WindowsDriver : ConsoleDriver if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.MainLoop.AddIdle ( - () => - { - Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag)); + Application.MainLoop!.AddIdle ( + () => + { + Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag)); - return false; - }); + return false; + }); } } else if (_lastMouseButtonPressed != null @@ -1254,420 +1253,3 @@ internal class WindowsDriver : ConsoleDriver }; } } - -/// -/// Mainloop intended to be used with the , and can -/// only be used on Windows. -/// -/// -/// This implementation is used for WindowsDriver. -/// -internal class WindowsMainLoop : IMainLoopDriver -{ - /// - /// Invoked when the window is changed. - /// - public EventHandler WinChanged; - - private readonly ConsoleDriver _consoleDriver; - private readonly ManualResetEventSlim _eventReady = new (false); - - // The records that we keep fetching - private readonly ConcurrentQueue _resultQueue = new (); - internal readonly ManualResetEventSlim _waitForProbe = new (false); - private readonly WindowsConsole _winConsole; - private CancellationTokenSource _eventReadyTokenSource = new (); - private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - private MainLoop _mainLoop; - - public WindowsMainLoop (ConsoleDriver consoleDriver = null) - { - _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); - - if (!ConsoleDriver.RunningUnitTests) - { - _winConsole = ((WindowsDriver)consoleDriver).WinConsole; - _winConsole._mainLoop = this; - } - } - - public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); - - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - _mainLoop = mainLoop; - - if (ConsoleDriver.RunningUnitTests) - { - return; - } - - Task.Run (WindowsInputHandler, _inputHandlerTokenSource.Token); -#if HACK_CHECK_WINCHANGED - Task.Run (CheckWinChange); -#endif - } - - void IMainLoopDriver.Wakeup () { _eventReady.Set (); } - - bool IMainLoopDriver.EventsPending () - { - _waitForProbe.Set (); -#if HACK_CHECK_WINCHANGED - _winChange.Set (); -#endif - if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) - { - return true; - } - - try - { - if (!_eventReadyTokenSource.IsCancellationRequested) - { - // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there - // are no timers, but there IS an idle handler waiting. - _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return true; - } - finally - { - _eventReady.Reset (); - } - - if (!_eventReadyTokenSource.IsCancellationRequested) - { -#if HACK_CHECK_WINCHANGED - return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _) || _winChanged; -#else - return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); -#endif - } - - _eventReadyTokenSource.Dispose (); - _eventReadyTokenSource = new CancellationTokenSource (); - - return true; - } - - void IMainLoopDriver.Iteration () - { - while (_resultQueue.Count > 0) - { - if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord [] inputRecords)) - { - if (inputRecords is { Length: > 0 }) - { - ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]); - } - } - } -#if HACK_CHECK_WINCHANGED - if (_winChanged) - { - _winChanged = false; - WinChanged?.Invoke (this, new SizeChangedEventArgs (_windowSize)); - } -#endif - } - - void IMainLoopDriver.TearDown () - { - _inputHandlerTokenSource?.Cancel (); - _inputHandlerTokenSource?.Dispose (); - - if (_winConsole is { }) - { - var numOfEvents = _winConsole.GetNumberOfConsoleInputEvents (); - - if (numOfEvents > 0) - { - _winConsole.FlushConsoleInputBuffer (); - //Debug.WriteLine ($"Flushed {numOfEvents} events."); - } - } - - _waitForProbe?.Dispose (); - - _resultQueue?.Clear (); - - _eventReadyTokenSource?.Cancel (); - _eventReadyTokenSource?.Dispose (); - _eventReady?.Dispose (); - -#if HACK_CHECK_WINCHANGED - _winChange?.Dispose (); -#endif - - _mainLoop = null; - } - - internal bool _forceRead; - - private void WindowsInputHandler () - { - while (_mainLoop is { }) - { - try - { - if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) - { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); - } - } - catch (OperationCanceledException) - { - // Wakes the _waitForProbe if it's waiting - _waitForProbe.Set (); - - return; - } - finally - { - // If IsCancellationRequested is true the code after - // the `finally` block will not be executed. - if (!_inputHandlerTokenSource.IsCancellationRequested) - { - _waitForProbe.Reset (); - } - } - - if (_resultQueue?.Count == 0 || _forceRead) - { - while (!_inputHandlerTokenSource.IsCancellationRequested) - { - WindowsConsole.InputRecord [] inpRec = _winConsole.ReadConsoleInput (); - - if (inpRec is { }) - { - _resultQueue!.Enqueue (inpRec); - - break; - } - - if (!_forceRead) - { - try - { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); - } - catch (OperationCanceledException) - { } - } - } - } - - _eventReady.Set (); - } - } - -#if HACK_CHECK_WINCHANGED - private readonly ManualResetEventSlim _winChange = new (false); - private bool _winChanged; - private Size _windowSize; - private void CheckWinChange () - { - while (_mainLoop is { }) - { - _winChange.Wait (); - _winChange.Reset (); - - // Check if the window size changed every half second. - // We do this to minimize the weird tearing seen on Windows when resizing the console - while (_mainLoop is { }) - { - Task.Delay (500).Wait (); - _windowSize = _winConsole.GetConsoleBufferWindow (out _); - - if (_windowSize != Size.Empty - && (_windowSize.Width != _consoleDriver.Cols - || _windowSize.Height != _consoleDriver.Rows)) - { - break; - } - } - - _winChanged = true; - _eventReady.Set (); - } - } -#endif -} - -internal class WindowsClipboard : ClipboardBase -{ - private const uint CF_UNICODE_TEXT = 13; - - public override bool IsSupported { get; } = CheckClipboardIsAvailable (); - - private static bool CheckClipboardIsAvailable () - { - // Attempt to open the clipboard - if (OpenClipboard (nint.Zero)) - { - // Clipboard is available - // Close the clipboard after use - CloseClipboard (); - - return true; - } - // Clipboard is not available - return false; - } - - protected override string GetClipboardDataImpl () - { - try - { - if (!OpenClipboard (nint.Zero)) - { - return string.Empty; - } - - nint handle = GetClipboardData (CF_UNICODE_TEXT); - - if (handle == nint.Zero) - { - return string.Empty; - } - - nint pointer = nint.Zero; - - try - { - pointer = GlobalLock (handle); - - if (pointer == nint.Zero) - { - return string.Empty; - } - - int size = GlobalSize (handle); - var buff = new byte [size]; - - Marshal.Copy (pointer, buff, 0, size); - - return Encoding.Unicode.GetString (buff).TrimEnd ('\0'); - } - finally - { - if (pointer != nint.Zero) - { - GlobalUnlock (handle); - } - } - } - finally - { - CloseClipboard (); - } - } - - protected override void SetClipboardDataImpl (string text) - { - OpenClipboard (); - - EmptyClipboard (); - nint hGlobal = default; - - try - { - int bytes = (text.Length + 1) * 2; - hGlobal = Marshal.AllocHGlobal (bytes); - - if (hGlobal == default (nint)) - { - ThrowWin32 (); - } - - nint target = GlobalLock (hGlobal); - - if (target == default (nint)) - { - ThrowWin32 (); - } - - try - { - Marshal.Copy (text.ToCharArray (), 0, target, text.Length); - } - finally - { - GlobalUnlock (target); - } - - if (SetClipboardData (CF_UNICODE_TEXT, hGlobal) == default (nint)) - { - ThrowWin32 (); - } - - hGlobal = default (nint); - } - finally - { - if (hGlobal != default (nint)) - { - Marshal.FreeHGlobal (hGlobal); - } - - CloseClipboard (); - } - } - - [DllImport ("user32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - private static extern bool CloseClipboard (); - - [DllImport ("user32.dll")] - private static extern bool EmptyClipboard (); - - [DllImport ("user32.dll", SetLastError = true)] - private static extern nint GetClipboardData (uint uFormat); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern nint GlobalLock (nint hMem); - - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern int GlobalSize (nint handle); - - [DllImport ("kernel32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - private static extern bool GlobalUnlock (nint hMem); - - [DllImport ("User32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - private static extern bool IsClipboardFormatAvailable (uint format); - - private void OpenClipboard () - { - var num = 10; - - while (true) - { - if (OpenClipboard (default (nint))) - { - break; - } - - if (--num == 0) - { - ThrowWin32 (); - } - - Thread.Sleep (100); - } - } - - [DllImport ("user32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - private static extern bool OpenClipboard (nint hWndNewOwner); - - [DllImport ("user32.dll", SetLastError = true)] - private static extern nint SetClipboardData (uint uFormat, nint data); - - private void ThrowWin32 () { throw new Win32Exception (Marshal.GetLastWin32Error ()); } -} diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs new file mode 100644 index 000000000..33aca9704 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -0,0 +1,248 @@ +#nullable enable +using System.Collections.Concurrent; + +namespace Terminal.Gui; + +/// +/// Mainloop intended to be used with the , and can +/// only be used on Windows. +/// +/// +/// This implementation is used for WindowsDriver. +/// +internal class WindowsMainLoop : IMainLoopDriver +{ + /// + /// Invoked when the window is changed. + /// + public EventHandler? WinChanged; + + private readonly ConsoleDriver _consoleDriver; + private readonly ManualResetEventSlim _eventReady = new (false); + + // The records that we keep fetching + private readonly ConcurrentQueue _resultQueue = new (); + internal readonly ManualResetEventSlim _waitForProbe = new (false); + private readonly WindowsConsole? _winConsole; + private CancellationTokenSource _eventReadyTokenSource = new (); + private readonly CancellationTokenSource _inputHandlerTokenSource = new (); + private MainLoop? _mainLoop; + + public WindowsMainLoop (ConsoleDriver consoleDriver) + { + _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); + + if (!ConsoleDriver.RunningUnitTests) + { + _winConsole = ((WindowsDriver)consoleDriver).WinConsole; + _winConsole!._mainLoop = this; + } + } + + public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); + + void IMainLoopDriver.Setup (MainLoop mainLoop) + { + _mainLoop = mainLoop; + + if (ConsoleDriver.RunningUnitTests) + { + return; + } + + Task.Run (WindowsInputHandler, _inputHandlerTokenSource.Token); +#if HACK_CHECK_WINCHANGED + Task.Run (CheckWinChange); +#endif + } + + void IMainLoopDriver.Wakeup () { _eventReady.Set (); } + + bool IMainLoopDriver.EventsPending () + { + _waitForProbe.Set (); +#if HACK_CHECK_WINCHANGED + _winChange.Set (); +#endif + if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) + { + return true; + } + + try + { + if (!_eventReadyTokenSource.IsCancellationRequested) + { + // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there + // are no timers, but there IS an idle handler waiting. + _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return true; + } + finally + { + _eventReady.Reset (); + } + + if (!_eventReadyTokenSource.IsCancellationRequested) + { +#if HACK_CHECK_WINCHANGED + return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _) || _winChanged; +#else + return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); +#endif + } + + _eventReadyTokenSource.Dispose (); + _eventReadyTokenSource = new CancellationTokenSource (); + + return true; + } + + void IMainLoopDriver.Iteration () + { + while (_resultQueue.Count > 0) + { + if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord []? inputRecords)) + { + if (inputRecords is { Length: > 0 }) + { + ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]); + } + } + } +#if HACK_CHECK_WINCHANGED + if (_winChanged) + { + _winChanged = false; + WinChanged?.Invoke (this, new SizeChangedEventArgs (_windowSize)); + } +#endif + } + + void IMainLoopDriver.TearDown () + { + _inputHandlerTokenSource.Cancel (); + _inputHandlerTokenSource.Dispose (); + + if (_winConsole is { }) + { + var numOfEvents = _winConsole.GetNumberOfConsoleInputEvents (); + + if (numOfEvents > 0) + { + _winConsole.FlushConsoleInputBuffer (); + //Debug.WriteLine ($"Flushed {numOfEvents} events."); + } + } + + _waitForProbe.Dispose (); + + _resultQueue.Clear (); + + _eventReadyTokenSource.Cancel (); + _eventReadyTokenSource.Dispose (); + _eventReady.Dispose (); + +#if HACK_CHECK_WINCHANGED + _winChange?.Dispose (); +#endif + + _mainLoop = null; + } + + internal bool _forceRead; + + private void WindowsInputHandler () + { + while (_mainLoop is { }) + { + try + { + if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) + { + _waitForProbe.Wait (_inputHandlerTokenSource.Token); + } + } + catch (OperationCanceledException) + { + // Wakes the _waitForProbe if it's waiting + _waitForProbe.Set (); + + return; + } + finally + { + // If IsCancellationRequested is true the code after + // the `finally` block will not be executed. + if (!_inputHandlerTokenSource.IsCancellationRequested) + { + _waitForProbe.Reset (); + } + } + + if (_resultQueue?.Count == 0 || _forceRead) + { + while (!_inputHandlerTokenSource.IsCancellationRequested) + { + WindowsConsole.InputRecord [] inpRec = _winConsole.ReadConsoleInput (); + + if (inpRec is { }) + { + _resultQueue!.Enqueue (inpRec); + + break; + } + + if (!_forceRead) + { + try + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + catch (OperationCanceledException) + { } + } + } + } + + _eventReady.Set (); + } + } + +#if HACK_CHECK_WINCHANGED + private readonly ManualResetEventSlim _winChange = new (false); + private bool _winChanged; + private Size _windowSize; + private void CheckWinChange () + { + while (_mainLoop is { }) + { + _winChange.Wait (); + _winChange.Reset (); + + // Check if the window size changed every half second. + // We do this to minimize the weird tearing seen on Windows when resizing the console + while (_mainLoop is { }) + { + Task.Delay (500).Wait (); + _windowSize = _winConsole.GetConsoleBufferWindow (out _); + + if (_windowSize != Size.Empty + && (_windowSize.Width != _consoleDriver.Cols + || _windowSize.Height != _consoleDriver.Rows)) + { + break; + } + } + + _winChanged = true; + _eventReady.Set (); + } + } +#endif +} + From 873b5783ef162da7212d1df2c358b30baee17f11 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 15:41:58 +0000 Subject: [PATCH 089/151] #nullable enable. --- .../ConsoleDrivers/NetDriver/NetDriver.cs | 31 ++++++++++------- .../ConsoleDrivers/NetDriver/NetEvents.cs | 32 ++++++++--------- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 34 +++++++++---------- .../NetDriver/NetWinVTConsole.cs | 3 +- 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 0422f548f..73ec87d41 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -1,4 +1,4 @@ -// TODO: #nullable enable +#nullable enable // // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. // @@ -14,7 +14,7 @@ namespace Terminal.Gui; internal class NetDriver : ConsoleDriver { public bool IsWinPlatform { get; private set; } - public NetWinVTConsole NetWinConsole { get; private set; } + public NetWinVTConsole? NetWinConsole { get; private set; } public override void Refresh () { @@ -61,7 +61,7 @@ internal class NetDriver : ConsoleDriver if (RunningUnitTests || _winSizeChanging || Console.WindowHeight < 1 - || Contents.Length != Rows * Cols + || Contents?.Length != Rows * Cols || Rows != Console.WindowHeight) { return; @@ -85,7 +85,7 @@ internal class NetDriver : ConsoleDriver return; } - if (!_dirtyLines [row]) + if (!_dirtyLines! [row]) { continue; } @@ -129,7 +129,7 @@ internal class NetDriver : ConsoleDriver lastCol = col; } - Attribute attr = Contents [row, col].Attribute.Value; + Attribute attr = Contents [row, col].Attribute!.Value; // Performance: Only send the escape sequence if the attribute has changed. if (attr != redrawAttr) @@ -229,7 +229,7 @@ internal class NetDriver : ConsoleDriver #region Init/End/MainLoop - internal NetMainLoop _mainLoopDriver; + internal NetMainLoop? _mainLoopDriver; internal override MainLoop Init () { @@ -339,7 +339,7 @@ internal class NetDriver : ConsoleDriver Left = 0; Cols = inputEvent.WindowSizeEvent.Size.Width; Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); - ; + ResizeScreen (); ClearContents (); _winSizeChanging = false; @@ -727,16 +727,21 @@ internal class NetDriver : ConsoleDriver #region Low-Level DotNet tuff private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + private CancellationTokenSource? _ansiResponseTokenSource; /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { - if (_mainLoopDriver is null) + lock (ansiRequest._responseLock) { - return string.Empty; + if (_mainLoopDriver is null) + { + return string.Empty; + } } + _ansiResponseTokenSource ??= new (); + try { lock (ansiRequest._responseLock) @@ -765,12 +770,12 @@ internal class NetDriver : ConsoleDriver { _mainLoopDriver._netEvents._forceRead = false; - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request)) + if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.Response)) { - lock (request!.AnsiRequest._responseLock) + lock (request.AnsiRequest._responseLock) { // Bad request or no response at all _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 9976c0136..6ce0bbe26 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -1,4 +1,4 @@ -// TODO: #nullable enable +#nullable enable using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; @@ -6,10 +6,10 @@ namespace Terminal.Gui; internal class NetEvents : IDisposable { - private CancellationTokenSource _inputReadyCancellationTokenSource; + private CancellationTokenSource? _inputReadyCancellationTokenSource; private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); private readonly ConsoleDriver _consoleDriver; - private ConsoleKeyInfo [] _cki; + private ConsoleKeyInfo []? _cki; private bool _isEscSeq; #if PROCESS_REQUEST bool _neededProcessRequest; @@ -62,9 +62,9 @@ internal class NetEvents : IDisposable { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) { - lock (seqReqStatus!.AnsiRequest._responseLock) + lock (seqReqStatus.AnsiRequest._responseLock) { EscSeqRequests.Statuses.TryDequeue (out _); @@ -319,7 +319,7 @@ internal class NetEvents : IDisposable out bool isMouse, out List mouseFlags, out Point pos, - out AnsiEscapeSequenceRequestStatus seqReqStatus, + out AnsiEscapeSequenceRequestStatus? seqReqStatus, (f, p) => HandleMouseEvent (MapMouseFlags (f), p) ); @@ -350,7 +350,7 @@ internal class NetEvents : IDisposable if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) { - if (EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus result)) + if (EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) { lock (result.AnsiRequest._responseLock) { @@ -504,7 +504,7 @@ internal class NetEvents : IDisposable return mbs; } - private Point _lastCursorPosition; + //private Point _lastCursorPosition; //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) //{ @@ -651,15 +651,15 @@ internal class NetEvents : IDisposable public readonly override string ToString () { - return EventType switch - { - EventType.Key => ToString (ConsoleKeyInfo), - EventType.Mouse => MouseEvent.ToString (), + return (EventType switch + { + EventType.Key => ToString (ConsoleKeyInfo), + EventType.Mouse => MouseEvent.ToString (), - //EventType.WindowSize => WindowSize.ToString (), - //EventType.RequestResponse => RequestResponse.ToString (), - _ => "Unknown event type: " + EventType - }; + //EventType.WindowSize => WindowSize.ToString (), + //EventType.RequestResponse => RequestResponse.ToString (), + _ => "Unknown event type: " + EventType + })!; } /// Prints a ConsoleKeyInfoEx structure diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 6e68bf8d5..df6b63203 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +#nullable enable +using System.Collections.Concurrent; namespace Terminal.Gui; @@ -9,22 +10,22 @@ namespace Terminal.Gui; /// This implementation is used for NetDriver. internal class NetMainLoop : IMainLoopDriver { - internal NetEvents _netEvents; + internal NetEvents? _netEvents; /// Invoked when a Key is pressed. - internal Action ProcessInput; + internal Action? ProcessInput; private readonly ManualResetEventSlim _eventReady = new (false); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ()); private readonly CancellationTokenSource _eventReadyTokenSource = new (); - private MainLoop _mainLoop; + private MainLoop? _mainLoop; /// Initializes the class with the console driver. /// Passing a consoleDriver is provided to capture windows resizing. /// The console driver used by this Net main loop. /// - public NetMainLoop (ConsoleDriver consoleDriver = null) + public NetMainLoop (ConsoleDriver consoleDriver) { ArgumentNullException.ThrowIfNull (consoleDriver); @@ -47,7 +48,7 @@ internal class NetMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - if (_resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; } @@ -88,24 +89,21 @@ internal class NetMainLoop : IMainLoopDriver // Always dequeue even if it's null and invoke if isn't null if (_resultQueue.TryTake (out NetEvents.InputResult dequeueResult)) { - if (dequeueResult is { }) - { - ProcessInput?.Invoke (dequeueResult); - } + ProcessInput?.Invoke (dequeueResult); } } } void IMainLoopDriver.TearDown () { - _inputHandlerTokenSource?.Cancel (); - _inputHandlerTokenSource?.Dispose (); - _eventReadyTokenSource?.Cancel (); - _eventReadyTokenSource?.Dispose (); + _inputHandlerTokenSource.Cancel (); + _inputHandlerTokenSource.Dispose (); + _eventReadyTokenSource.Cancel (); + _eventReadyTokenSource.Dispose (); - _eventReady?.Dispose (); + _eventReady.Dispose (); - _resultQueue?.Dispose(); + _resultQueue.Dispose(); _netEvents?.Dispose (); _netEvents = null; @@ -123,9 +121,9 @@ internal class NetMainLoop : IMainLoopDriver return; } - if (_resultQueue?.Count == 0 || _netEvents._forceRead) + if (_resultQueue?.Count == 0 || _netEvents!._forceRead) { - NetEvents.InputResult? result = _netEvents.DequeueInput (); + NetEvents.InputResult? result = _netEvents!.DequeueInput (); if (result.HasValue) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs index 81a9f6b68..98666f460 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +#nullable enable +using System.Runtime.InteropServices; namespace Terminal.Gui; From ea0cacf08086d9ddbbf697aa931fbbe7fc3c248a Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 16:14:51 +0000 Subject: [PATCH 090/151] There is no null values here. --- Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index df6b63203..624aec3ce 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -86,7 +86,6 @@ internal class NetMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { - // Always dequeue even if it's null and invoke if isn't null if (_resultQueue.TryTake (out NetEvents.InputResult dequeueResult)) { ProcessInput?.Invoke (dequeueResult); From 2124ad3b1ee3531c2736c55adeb9e34b76b64c42 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Nov 2024 16:31:07 +0000 Subject: [PATCH 091/151] Cleanup code. --- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 6ce0bbe26..e42fd138f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -19,7 +19,7 @@ internal class NetEvents : IDisposable public NetEvents (ConsoleDriver consoleDriver) { _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); - _inputReadyCancellationTokenSource = new CancellationTokenSource (); + _inputReadyCancellationTokenSource = new (); Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); From a6af3aadb77056e95bbde2bec96ff0de144ab6ee Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Nov 2024 01:36:56 +0000 Subject: [PATCH 092/151] An huge improvements on drivers and bug fixes. --- .../AnsiEscapeSequenceRequest.cs | 104 +++--- .../AnsiEscapeSequenceRequestUtils.cs | 4 +- .../AnsiEscapeSequenceResponse.cs | 28 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 4 +- .../CursesDriver/CursesDriver.cs | 160 +++------ .../CursesDriver/UnixMainLoop.cs | 10 +- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 4 +- .../ConsoleDrivers/NetDriver/NetDriver.cs | 71 ++-- .../ConsoleDrivers/NetDriver/NetEvents.cs | 14 +- .../WindowsDriver/WindowsConsole.cs | 332 +++++++++++------- .../WindowsDriver/WindowsDriver.cs | 25 +- .../WindowsDriver/WindowsMainLoop.cs | 76 ++-- .../Scenarios/AnsiEscapeSequenceRequests.cs | 22 +- 13 files changed, 404 insertions(+), 450 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 1159ae37d..acb2c59a0 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -10,6 +10,22 @@ public class AnsiEscapeSequenceRequest { internal readonly object _responseLock = new (); // Per-instance lock + /// + /// Gets the response received from the request. + /// + public AnsiEscapeSequenceResponse? AnsiEscapeSequenceResponse { get; internal set; } + + /// + /// The value expected in the response after the CSI e.g. + /// + /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value + /// + /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, + /// + /// will be "8". + /// + public string? ExpectedResponseValue { get; init; } + /// /// Gets the request string to send e.g. see /// @@ -18,20 +34,6 @@ public class AnsiEscapeSequenceRequest /// public required string Request { get; init; } - // QUESTION: Could the type of this propperty be AnsiEscapeSequenceResponse? This would remove the - // QUESTION: removal of the redundant Rresponse, Terminator, and ExpectedRespnseValue properties from this class? - // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable? - /// - /// Gets the response received from the request. - /// - public string? Response { get; internal set; } - - /// - /// Raised when the console responds with an ANSI response code that matches the - /// - /// - public event EventHandler? ResponseReceived; - /// /// /// Gets the terminator that uniquely identifies the response received from @@ -52,47 +54,49 @@ public class AnsiEscapeSequenceRequest /// public required string Terminator { get; init; } + internal void RaiseResponseFromInput (string? response) + { + ProcessResponse (response); + + ResponseFromInput?.Invoke (this, AnsiEscapeSequenceResponse); + } + /// - /// Attempt an ANSI escape sequence request which may return a response or error. + /// Raised with the response object and validation. /// - /// The ANSI escape sequence to request. - /// - /// When this method returns , the response. will - /// be . - /// - /// A with the response, error, terminator, and value. - public static bool TryRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result) + internal event EventHandler? ResponseFromInput; + + /// + /// Process the of an ANSI escape sequence request. + /// + /// The response. + private void ProcessResponse (string? response) { var error = new StringBuilder (); var values = new string? [] { null }; try { - ConsoleDriver? driver = Application.Driver; - - // Send the ANSI escape sequence - ansiRequest.Response = driver?.WriteAnsiRequest (ansiRequest)!; - - if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc)) + if (!string.IsNullOrEmpty (response) && !response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc)) { - throw new InvalidOperationException ($"Invalid Response: {ansiRequest.Response}"); + throw new InvalidOperationException ($"Invalid Response: {response}"); } - if (string.IsNullOrEmpty (ansiRequest.Terminator)) + if (string.IsNullOrEmpty (Terminator)) { throw new InvalidOperationException ("Terminator request is empty."); } - if (string.IsNullOrEmpty (ansiRequest.Response)) + if (string.IsNullOrEmpty (response)) { throw new InvalidOperationException ("Response request is null."); } - if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1])) + if (!string.IsNullOrEmpty (response) && !response.EndsWith (Terminator [^1])) { - string resp = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response.Last ().ToString (); + string resp = string.IsNullOrEmpty (response) ? "" : response.Last ().ToString (); - throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{ansiRequest.Terminator [^1]}'"); + throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{Terminator [^1]}'"); } } catch (Exception ex) @@ -103,36 +107,16 @@ public class AnsiEscapeSequenceRequest { if (string.IsNullOrEmpty (error.ToString ())) { - (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ()); + (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (response?.ToCharArray ()); } } - AnsiEscapeSequenceResponse ansiResponse = new () + AnsiEscapeSequenceResponse = new () { - Response = ansiRequest.Response, Error = error.ToString (), - Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), ExpectedResponseValue = values [0] + Response = response, Error = error.ToString (), + Terminator = string.IsNullOrEmpty (response) ? "" : response [^1].ToString (), + ExpectedResponseValue = values [0], + Valid = string.IsNullOrWhiteSpace (error.ToString ()) && !string.IsNullOrWhiteSpace (response) }; - - // Invoke the event if it's subscribed - ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse); - - result = ansiResponse; - - return string.IsNullOrWhiteSpace (result.Error) && !string.IsNullOrWhiteSpace (result.Response); } - - /// - /// The value expected in the response after the CSI e.g. - /// - /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value - /// - /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, - /// will be "8". - /// - public string? ExpectedResponseValue { get; init; } - - internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string? response) { ResponseFromInput?.Invoke (ansiRequest, response); } - - // QUESTION: What is this for? Please provide a descriptive comment. - internal event EventHandler? ResponseFromInput; } diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index ca8533f77..053aacf1f 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -183,7 +183,7 @@ public static class AnsiEscapeSequenceRequestUtils /// /// Decodes an ANSI escape sequence. /// - /// The which may contain a request. + /// The which may contain a request. /// The which may change. /// The which may change. /// The array. @@ -195,7 +195,7 @@ public static class AnsiEscapeSequenceRequestUtils /// Indicates if the escape sequence is a mouse event. /// The button state. /// The position. - /// The object. + /// The object. /// The handler that will process the event. public static void DecodeEscSeq ( AnsiEscapeSequenceRequests? escSeqRequests, diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs index 9a5a2ec8d..104cd760a 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs @@ -2,11 +2,11 @@ namespace Terminal.Gui; /// -/// Describes a response received from the console as a result of a request being sent via . +/// Describes a response received from the console as a result of a request being sent via +/// . /// public class AnsiEscapeSequenceResponse { - // QUESTION: Should this be nullable to indicate there was no error, or is string.Empty sufficient? /// /// Gets the error string received from e.g. see /// @@ -16,7 +16,18 @@ public class AnsiEscapeSequenceResponse /// public required string Error { get; init; } - // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable? + /// + /// The value expected in the response after the CSI e.g. + /// + /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value + /// + /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, + /// + /// will be "8". + /// + + public string? ExpectedResponseValue { get; init; } + /// /// Gets the Response string received from e.g. see /// @@ -26,7 +37,6 @@ public class AnsiEscapeSequenceResponse /// public required string? Response { get; init; } - // QUESTION: Does string.Empty indicate no terminator expected? If not, perhaps make this property nullable? /// /// /// Gets the terminator that uniquely identifies the response received from @@ -48,13 +58,7 @@ public class AnsiEscapeSequenceResponse public required string Terminator { get; init; } /// - /// The value expected in the response after the CSI e.g. - /// - /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value - /// - /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, - /// will be "8". + /// Gets if the request has a valid response. /// - - public string? ExpectedResponseValue { get; init; } + public bool Valid { get; internal set; } } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 885f907a0..067e00ca8 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -45,7 +45,7 @@ public abstract class ConsoleDriver /// /// The object. /// The request response. - public abstract string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); + public abstract bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API? @@ -53,7 +53,7 @@ public abstract class ConsoleDriver /// Provide proper writing to send escape sequence recognized by the . /// /// - public abstract void WriteRaw (string ansi); + internal abstract void WriteRaw (string ansi); #endregion ANSI Esc Sequence Handling diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 866bfa496..8b4a7c215 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -1,4 +1,4 @@ -// TODO: #nullable enable +#nullable enable // // Driver.cs: Curses-based Driver // @@ -10,7 +10,7 @@ using Unix.Terminal; namespace Terminal.Gui; -/// A Linux/Mac driver based on the Curses libary. +/// A Linux/Mac driver based on the Curses library. internal class CursesDriver : ConsoleDriver { public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; } @@ -94,7 +94,7 @@ internal class CursesDriver : ConsoleDriver { for (var row = 0; row < Rows; row++) { - if (!_dirtyLines [row]) + if (!_dirtyLines! [row]) { continue; } @@ -103,7 +103,7 @@ internal class CursesDriver : ConsoleDriver for (var col = 0; col < Cols; col++) { - if (Contents [row, col].IsDirty == false) + if (Contents! [row, col].IsDirty == false) { continue; } @@ -147,14 +147,14 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests) { Curses.move (Row, Col); - _window.wrefresh (); + _window?.wrefresh (); } } else { if (RunningUnitTests || Console.WindowHeight < 1 - || Contents.Length != Rows * Cols + || Contents!.Length != Rows * Cols || Rows != Console.WindowHeight) { return; @@ -178,7 +178,7 @@ internal class CursesDriver : ConsoleDriver return; } - if (!_dirtyLines [row]) + if (!_dirtyLines! [row]) { continue; } @@ -222,7 +222,7 @@ internal class CursesDriver : ConsoleDriver lastCol = col; } - Attribute attr = Contents [row, col].Attribute.Value; + Attribute attr = Contents [row, col].Attribute!.Value; // Performance: Only send the escape sequence if the attribute has changed. if (attr != redrawAttr) @@ -515,7 +515,7 @@ internal class CursesDriver : ConsoleDriver } else { - _mainLoopDriver.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); } } } @@ -530,22 +530,22 @@ internal class CursesDriver : ConsoleDriver if (consoleKey == ConsoleKey.Packet) { - var mod = new ConsoleModifiers (); + //var mod = new ConsoleModifiers (); - if (shift) - { - mod |= ConsoleModifiers.Shift; - } + //if (shift) + //{ + // mod |= ConsoleModifiers.Shift; + //} - if (alt) - { - mod |= ConsoleModifiers.Alt; - } + //if (alt) + //{ + // mod |= ConsoleModifiers.Alt; + //} - if (control) - { - mod |= ConsoleModifiers.Control; - } + //if (control) + //{ + // mod |= ConsoleModifiers.Control; + //} var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); @@ -562,84 +562,6 @@ internal class CursesDriver : ConsoleDriver //OnKeyPressed (new KeyEventArgsEventArgs (key)); } - // TODO: Unused- Remove - private static KeyCode MapCursesKey (int cursesKey) - { - switch (cursesKey) - { - case Curses.KeyF1: return KeyCode.F1; - case Curses.KeyF2: return KeyCode.F2; - case Curses.KeyF3: return KeyCode.F3; - case Curses.KeyF4: return KeyCode.F4; - case Curses.KeyF5: return KeyCode.F5; - case Curses.KeyF6: return KeyCode.F6; - case Curses.KeyF7: return KeyCode.F7; - case Curses.KeyF8: return KeyCode.F8; - case Curses.KeyF9: return KeyCode.F9; - case Curses.KeyF10: return KeyCode.F10; - case Curses.KeyF11: return KeyCode.F11; - case Curses.KeyF12: return KeyCode.F12; - case Curses.KeyUp: return KeyCode.CursorUp; - case Curses.KeyDown: return KeyCode.CursorDown; - case Curses.KeyLeft: return KeyCode.CursorLeft; - case Curses.KeyRight: return KeyCode.CursorRight; - case Curses.KeyHome: return KeyCode.Home; - case Curses.KeyEnd: return KeyCode.End; - case Curses.KeyNPage: return KeyCode.PageDown; - case Curses.KeyPPage: return KeyCode.PageUp; - case Curses.KeyDeleteChar: return KeyCode.Delete; - case Curses.KeyInsertChar: return KeyCode.Insert; - case Curses.KeyTab: return KeyCode.Tab; - case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask; - case Curses.KeyBackspace: return KeyCode.Backspace; - case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask; - case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask; - case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask; - case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask; - case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask; - case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask; - case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask; - case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask; - case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask; - case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask; - case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask; - case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask; - case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask; - case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask; - case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask; - case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask; - case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask; - case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask; - case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask; - case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask; - case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask; - case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask; - case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask; - case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask; - case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask; - case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask; - case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask; - case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask; - case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask; - default: return KeyCode.Null; - } - } - #endregion Keyboard Support #region Mouse Support @@ -672,8 +594,8 @@ internal class CursesDriver : ConsoleDriver #region Init/End/MainLoop - public Curses.Window _window; - private UnixMainLoop _mainLoopDriver; + public Curses.Window? _window; + private UnixMainLoop? _mainLoopDriver; internal override MainLoop Init () { @@ -801,7 +723,7 @@ internal class CursesDriver : ConsoleDriver break; case UnixMainLoop.EventType.WindowSize: Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height); - ProcessWinChange (inputEvent.WindowSizeEvent.Size); + ProcessWinChange (size); break; default: @@ -821,7 +743,7 @@ internal class CursesDriver : ConsoleDriver { _ansiResponseTokenSource?.Cancel (); _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse?.Dispose (); + _waitAnsiResponse.Dispose (); StopReportingMouseMoves (); SetCursorVisibility (CursorVisibility.Default); @@ -860,27 +782,29 @@ internal class CursesDriver : ConsoleDriver private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + private CancellationTokenSource? _ansiResponseTokenSource; /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { if (_mainLoopDriver is null) { - return string.Empty; + return false; } + _ansiResponseTokenSource ??= new (); + try { lock (ansiRequest._responseLock) { ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.Response); + { + Debug.Assert (s == ansiRequest); + Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - _waitAnsiResponse.Set (); - }; + _waitAnsiResponse.Set (); + }; _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); @@ -896,19 +820,19 @@ internal class CursesDriver : ConsoleDriver } catch (OperationCanceledException) { - return string.Empty; + return false; } lock (ansiRequest._responseLock) { _mainLoopDriver._forceRead = false; - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request)) + if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.Response)) + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { - lock (request!.AnsiRequest._responseLock) + lock (request.AnsiRequest._responseLock) { // Bad request or no response at all _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); @@ -918,12 +842,12 @@ internal class CursesDriver : ConsoleDriver _waitAnsiResponse.Reset (); - return ansiRequest.Response; + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; } } /// - public override void WriteRaw (string ansi) { _mainLoopDriver.WriteRaw (ansi); } + internal override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); } } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 68d6df13a..203f67770 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -242,13 +242,13 @@ internal class UnixMainLoop : IMainLoopDriver { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse is { } && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse.Response)) { lock (seqReqStatus!.AnsiRequest._responseLock) { EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); } } @@ -331,8 +331,7 @@ internal class UnixMainLoop : IMainLoopDriver lock (seqReqStatus.AnsiRequest._responseLock) { - seqReqStatus.AnsiRequest.Response = ckiString; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString); } return; @@ -344,8 +343,7 @@ internal class UnixMainLoop : IMainLoopDriver { lock (result.AnsiRequest._responseLock) { - result.AnsiRequest.Response = AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator; - result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); + result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null; } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 24d39f4c1..849e13316 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -393,10 +393,10 @@ public class FakeDriver : ConsoleDriver } /// - public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } + public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } /// - public override void WriteRaw (string ansi) { throw new NotImplementedException (); } + internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } public void SetBufferSize (int width, int height) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 73ec87d41..b61ed5693 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -4,9 +4,7 @@ // using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; using static Terminal.Gui.NetEvents; namespace Terminal.Gui; @@ -367,7 +365,7 @@ internal class NetDriver : ConsoleDriver _ansiResponseTokenSource?.Cancel (); _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse?.Dispose (); + _waitAnsiResponse.Dispose (); if (!RunningUnitTests) { @@ -406,19 +404,19 @@ internal class NetDriver : ConsoleDriver private const int COLOR_WHITE = 37; private const int COLOR_YELLOW = 33; - // Cache the list of ConsoleColor values. - [UnconditionalSuppressMessage ( - "AOT", - "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", - Justification = "")] - private static readonly HashSet ConsoleColorValues = new ( - Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (c => (int)c) - ); + //// Cache the list of ConsoleColor values. + //[UnconditionalSuppressMessage ( + // "AOT", + // "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", + // Justification = "")] + //private static readonly HashSet ConsoleColorValues = new ( + // Enum.GetValues (typeof (ConsoleColor)) + // .OfType () + // .Select (c => (int)c) + // ); // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console. - private static readonly Dictionary colorMap = new () + private static readonly Dictionary _colorMap = new () { { ConsoleColor.Black, COLOR_BLACK }, { ConsoleColor.DarkBlue, COLOR_BLUE }, @@ -441,7 +439,7 @@ internal class NetDriver : ConsoleDriver // Map a ConsoleColor to a platform dependent value. private int MapColors (ConsoleColor color, bool isForeground = true) { - return colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; + return _colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; } #endregion @@ -705,22 +703,22 @@ internal class NetDriver : ConsoleDriver { } } - private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) - { - if (consoleKeyInfo.Key != ConsoleKey.Packet) - { - return consoleKeyInfo; - } + //private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + //{ + // if (consoleKeyInfo.Key != ConsoleKey.Packet) + // { + // return consoleKeyInfo; + // } - ConsoleModifiers mod = consoleKeyInfo.Modifiers; - bool shift = (mod & ConsoleModifiers.Shift) != 0; - bool alt = (mod & ConsoleModifiers.Alt) != 0; - bool control = (mod & ConsoleModifiers.Control) != 0; + // ConsoleModifiers mod = consoleKeyInfo.Modifiers; + // bool shift = (mod & ConsoleModifiers.Shift) != 0; + // bool alt = (mod & ConsoleModifiers.Alt) != 0; + // bool control = (mod & ConsoleModifiers.Control) != 0; - ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); + // ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); - return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); - } + // return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); + //} #endregion Keyboard Handling @@ -730,13 +728,13 @@ internal class NetDriver : ConsoleDriver private CancellationTokenSource? _ansiResponseTokenSource; /// - public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { lock (ansiRequest._responseLock) { if (_mainLoopDriver is null) { - return string.Empty; + return false; } } @@ -749,21 +747,20 @@ internal class NetDriver : ConsoleDriver ansiRequest.ResponseFromInput += (s, e) => { Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.Response); + Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); _waitAnsiResponse.Set (); }; - _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest); + _mainLoopDriver._netEvents!.EscSeqRequests.Add (ansiRequest); _mainLoopDriver._netEvents._forceRead = true; } - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); } catch (OperationCanceledException) { - return string.Empty; + return false; } lock (ansiRequest._responseLock) @@ -773,7 +770,7 @@ internal class NetDriver : ConsoleDriver if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.Response)) + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (request.AnsiRequest._responseLock) { @@ -785,12 +782,12 @@ internal class NetDriver : ConsoleDriver _waitAnsiResponse.Reset (); - return ansiRequest.Response; + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; } } /// - public override void WriteRaw (string ansi) { throw new NotImplementedException (); } + internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } private volatile bool _winSizeChanging; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index e42fd138f..e2906aa20 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -62,13 +62,13 @@ internal class NetEvents : IDisposable { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) + if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (seqReqStatus.AnsiRequest._responseLock) { EscSeqRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); } } @@ -150,7 +150,9 @@ internal class NetEvents : IDisposable if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar))) { ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; @@ -341,8 +343,7 @@ internal class NetEvents : IDisposable lock (seqReqStatus.AnsiRequest._responseLock) { - seqReqStatus.AnsiRequest.Response = ckiString; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString); } return; @@ -354,8 +355,7 @@ internal class NetEvents : IDisposable { lock (result.AnsiRequest._responseLock) { - result.AnsiRequest.Response = AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator; - result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); + result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null; } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index ff8d155b7..de2649747 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Collections.Concurrent; using System.ComponentModel; using System.Runtime.InteropServices; using Terminal.Gui.ConsoleDrivers; @@ -7,6 +8,8 @@ namespace Terminal.Gui; internal class WindowsConsole { + private CancellationTokenSource? _inputReadyCancellationTokenSource; + private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); internal WindowsMainLoop? _mainLoop; public const int STD_OUTPUT_HANDLE = -11; @@ -32,8 +35,206 @@ internal class WindowsConsole newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; ConsoleMode = newConsoleMode; + + _inputReadyCancellationTokenSource = new (); + Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); } + public InputRecord? DequeueInput () + { + while (_inputReadyCancellationTokenSource is { }) + { + try + { + return _inputQueue.Take (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return null; + } + } + + return null; + } + + public InputRecord? ReadConsoleInput () + { + const int BUFFER_SIZE = 1; + InputRecord inputRecord = default; + uint numberEventsRead = 0; + StringBuilder ansiSequence = new StringBuilder (); + bool readingSequence = false; + bool raisedResponse = false; + + while (!_inputReadyCancellationTokenSource!.IsCancellationRequested) + { + try + { + // Peek to check if there is any input available + if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0) + { + // Read the input since it is available + ReadConsoleInput ( + _inputHandle, + out inputRecord, + BUFFER_SIZE, + out numberEventsRead); + + if (inputRecord.EventType == EventType.Key) + { + KeyEventRecord keyEvent = inputRecord.KeyEvent; + + if (keyEvent.bKeyDown) + { + char inputChar = keyEvent.UnicodeChar; + + // Check if input is part of an ANSI escape sequence + if (inputChar == '\u001B') // Escape character + { + // Peek to check if there is any input available with key event and bKeyDown + if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0) + { + if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) + { + // It's really an ANSI request response + readingSequence = true; + ansiSequence.Clear (); // Start a new sequence + ansiSequence.Append (inputChar); + + continue; + } + } + } + else if (readingSequence) + { + ansiSequence.Append (inputChar); + + // Check if the sequence has ended with an expected command terminator + if (_mainLoop?.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus)) + { + // Finished reading the sequence and remove the enqueued request + _mainLoop.EscSeqRequests.Remove (seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + raisedResponse = true; + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); + } + } + + continue; + } + } + } + } + + if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus); + + lock (seqReqStatus!.AnsiRequest._responseLock) + { + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); + } + + _retries = 0; + } + else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) + { + if (_retries > 1) + { + if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (seqReqStatus.AnsiRequest._responseLock) + { + _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); + } + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + + if (numberEventsRead > 0) + { + return inputRecord; + } + + if (!_forceRead) + { + try + { + Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return null; + } + } + } + catch (Exception) + { + return null; + } + } + + return null; + } + + internal bool _forceRead; + + private void ProcessInputQueue () + { + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + try + { + if (_inputQueue.Count == 0 || _forceRead) + { + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + try + { + InputRecord? inpRec = ReadConsoleInput (); + + if (inpRec is { }) + { + _inputQueue.Add (inpRec.Value); + + break; + } + } + catch (OperationCanceledException) + { + return; + } + } + } + } + catch (OperationCanceledException) + { + return; + } + } + } + + private CharInfo []? _originalStdOutChars; public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) @@ -310,6 +511,10 @@ internal class WindowsConsole //} //_screenBuffer = nint.Zero; + + _inputReadyCancellationTokenSource?.Cancel (); + _inputReadyCancellationTokenSource?.Dispose (); + _inputReadyCancellationTokenSource = null; } //internal Size GetConsoleBufferWindow (out Point position) @@ -896,133 +1101,6 @@ internal class WindowsConsole private int _retries; - public InputRecord []? ReadConsoleInput () - { - const int BUFFER_SIZE = 1; - InputRecord inputRecord = default; - uint numberEventsRead = 0; - StringBuilder ansiSequence = new StringBuilder (); - bool readingSequence = false; - bool raisedResponse = false; - - while (true) - { - try - { - // Peek to check if there is any input available - if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0) - { - // Read the input since it is available - ReadConsoleInput ( - _inputHandle, - out inputRecord, - BUFFER_SIZE, - out numberEventsRead); - - if (inputRecord.EventType == EventType.Key) - { - KeyEventRecord keyEvent = inputRecord.KeyEvent; - - if (keyEvent.bKeyDown) - { - char inputChar = keyEvent.UnicodeChar; - - // Check if input is part of an ANSI escape sequence - if (inputChar == '\u001B') // Escape character - { - // Peek to check if there is any input available with key event and bKeyDown - if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0) - { - if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) - { - // It's really an ANSI request response - readingSequence = true; - ansiSequence.Clear (); // Start a new sequence - ansiSequence.Append (inputChar); - - continue; - } - } - } - else if (readingSequence) - { - ansiSequence.Append (inputChar); - - // Check if the sequence has ended with an expected command terminator - if (_mainLoop?.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus)) - { - // Finished reading the sequence and remove the enqueued request - _mainLoop.EscSeqRequests.Remove (seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) - { - raisedResponse = true; - seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); - } - } - - continue; - } - } - } - } - - if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) - { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) - { - seqReqStatus.AnsiRequest.Response = ansiSequence.ToString (); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); - } - - _retries = 0; - } - else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) - { - if (_retries > 1) - { - if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response)) - { - lock (seqReqStatus.AnsiRequest._responseLock) - { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } - - return (numberEventsRead == 0 - ? null - : [inputRecord])!; - } - catch (Exception) - { - return null; - } - } - } - #if false // Not needed on the constructor. Perhaps could be used on resizing. To study. [DllImport ("kernel32.dll", ExactSpelling = true)] static extern IntPtr GetConsoleWindow (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 616dc9fb0..07d0904ce 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -198,16 +198,18 @@ internal class WindowsDriver : ConsoleDriver } private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private readonly CancellationTokenSource _ansiResponseTokenSource = new (); + private CancellationTokenSource? _ansiResponseTokenSource; /// - public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) + public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { if (_mainLoopDriver is null) { - return string.Empty; + return false; } + _ansiResponseTokenSource ??= new (); + try { lock (ansiRequest._responseLock) @@ -215,7 +217,7 @@ internal class WindowsDriver : ConsoleDriver ansiRequest.ResponseFromInput += (s, e) => { Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.Response); + Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); _waitAnsiResponse.Set (); }; @@ -225,16 +227,11 @@ internal class WindowsDriver : ConsoleDriver _mainLoopDriver._forceRead = true; } - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - _mainLoopDriver._waitForProbe.Set (); - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); } catch (OperationCanceledException) { - return string.Empty; + return false; } lock (ansiRequest._responseLock) @@ -244,7 +241,7 @@ internal class WindowsDriver : ConsoleDriver if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.Response)) + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (request.AnsiRequest._responseLock) { @@ -256,11 +253,11 @@ internal class WindowsDriver : ConsoleDriver _waitAnsiResponse.Reset (); - return ansiRequest.Response; + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; } } - public override void WriteRaw (string ansi) + internal override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 33aca9704..fb167075d 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -21,8 +21,7 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); // The records that we keep fetching - private readonly ConcurrentQueue _resultQueue = new (); - internal readonly ManualResetEventSlim _waitForProbe = new (false); + private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ()); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -60,11 +59,10 @@ internal class WindowsMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForProbe.Set (); #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif - if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; } @@ -99,6 +97,7 @@ internal class WindowsMainLoop : IMainLoopDriver _eventReadyTokenSource.Dispose (); _eventReadyTokenSource = new CancellationTokenSource (); + // If cancellation was requested then always return true return true; } @@ -106,12 +105,9 @@ internal class WindowsMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { - if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord []? inputRecords)) + if (_resultQueue.TryTake (out WindowsConsole.InputRecord dequeueResult)) { - if (inputRecords is { Length: > 0 }) - { - ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]); - } + ((WindowsDriver)_consoleDriver).ProcessInput (dequeueResult); } } #if HACK_CHECK_WINCHANGED @@ -139,9 +135,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - _waitForProbe.Dispose (); - - _resultQueue.Clear (); + _resultQueue.Dispose (); _eventReadyTokenSource.Cancel (); _eventReadyTokenSource.Dispose (); @@ -162,54 +156,30 @@ internal class WindowsMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) + if (_inputHandlerTokenSource.IsCancellationRequested) { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); + return; + } + + if (_resultQueue?.Count == 0 || _forceRead) + { + WindowsConsole.InputRecord? result = _winConsole!.DequeueInput (); + + if (result.HasValue) + { + _resultQueue!.Add (result.Value); + } + } + + if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0) + { + _eventReady.Set (); } } catch (OperationCanceledException) { - // Wakes the _waitForProbe if it's waiting - _waitForProbe.Set (); - return; } - finally - { - // If IsCancellationRequested is true the code after - // the `finally` block will not be executed. - if (!_inputHandlerTokenSource.IsCancellationRequested) - { - _waitForProbe.Reset (); - } - } - - if (_resultQueue?.Count == 0 || _forceRead) - { - while (!_inputHandlerTokenSource.IsCancellationRequested) - { - WindowsConsole.InputRecord [] inpRec = _winConsole.ReadConsoleInput (); - - if (inpRec is { }) - { - _resultQueue!.Enqueue (inpRec); - - break; - } - - if (!_forceRead) - { - try - { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); - } - catch (OperationCanceledException) - { } - } - } - } - - _eventReady.Set (); } } diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 6fbc445c4..07c80f479 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -280,15 +280,14 @@ public sealed class AnsiEscapeSequenceRequests : Scenario ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text }; - bool success = AnsiEscapeSequenceRequest.TryRequest ( - ansiEscapeSequenceRequest, - out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse - ); + bool success = Application.Driver!.TryWriteAnsiRequest ( + ansiEscapeSequenceRequest + ); - tvResponse.Text = ansiEscapeSequenceResponse.Response ?? ""; - tvError.Text = ansiEscapeSequenceResponse.Error; - tvValue.Text = ansiEscapeSequenceResponse.ExpectedResponseValue ?? ""; - tvTerminator.Text = ansiEscapeSequenceResponse.Terminator; + tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? ""; + tvError.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Error ?? ""; + tvValue.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.ExpectedResponseValue ?? ""; + tvTerminator.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Terminator ?? ""; if (success) { @@ -334,8 +333,11 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void SendDar () { _sends.Add (DateTime.Now); - string result = Application.Driver.WriteAnsiRequest (AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes); - HandleResponse (result); + AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; + if (Application.Driver!.TryWriteAnsiRequest (ansiRequest)) + { + HandleResponse (ansiRequest.AnsiEscapeSequenceResponse.Response); + } } private void SetupGraph () From 8e9bb951d2d0759afa4f7ec539c46a9111c22d07 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Nov 2024 01:43:24 +0000 Subject: [PATCH 093/151] Code cleanup. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs index 8cc984523..e6fffcfc3 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs @@ -4,11 +4,10 @@ using System.Collections.Concurrent; namespace Terminal.Gui; -// QUESTION: Can this class be moved/refactored/combined with the new AnsiEscapeSequenceRequest/Response class? - // TODO: This class is a singleton. It should use the singleton pattern. /// -/// Manages ANSI Escape Sequence requests and responses. The list of contains the +/// Manages ANSI Escape Sequence requests and responses. The list of +/// contains the /// status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator). /// public class AnsiEscapeSequenceRequests @@ -50,7 +49,7 @@ public class AnsiEscapeSequenceRequests { Statuses.TryPeek (out seqReqStatus); - var result = seqReqStatus?.AnsiRequest.Terminator == terminator; + bool result = seqReqStatus?.AnsiRequest.Terminator == terminator; if (result) { @@ -64,7 +63,8 @@ public class AnsiEscapeSequenceRequests } /// - /// Removes a request defined by . If a matching is + /// Removes a request defined by . If a matching + /// is /// found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented. /// If the number of outstanding requests is 0, the is removed from /// . @@ -74,7 +74,7 @@ public class AnsiEscapeSequenceRequests { lock (Statuses) { - Statuses.TryDequeue (out var request); + Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? request); if (request != seqReqStatus) { From 012d47e28f345b76215641f03a14304f70ea34a9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Nov 2024 23:32:14 +0000 Subject: [PATCH 094/151] Fix IncompleteCkInfos bug. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs | 3 ++- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 2 +- UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index 053aacf1f..fe13359e0 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -1394,7 +1394,8 @@ public static class AnsiEscapeSequenceRequestUtils case ConsoleKey.Oem102: if (keyInfo.KeyChar == 0) { - // If the keyChar is 0, keyInfo.Key value is not a printable character. + // If the keyChar is 0, keyInfo.Key value is not a printable character. + System.Diagnostics.Debug.Assert (keyInfo.Key == 0); return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index e2906aa20..d15be6efa 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -125,7 +125,7 @@ internal class NetEvents : IDisposable if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) { - AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); + _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); } if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) diff --git a/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs index dbfc5286e..c7298d7c4 100644 --- a/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs +++ b/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs @@ -1547,12 +1547,14 @@ public class AnsiEscapeSequenceRequestUtilsTests if (index is null) { - Assert.Equal (result, AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki)); + cki = AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki); } else { - Assert.Equal (result, AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki, (int)index)); + cki = AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki, (int)index); } + + Assert.Equal (result, cki); } private void ClearAll () From 8c5832f8f143f058839783abe86941f74f1b87ae Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 9 Nov 2024 20:53:13 +0000 Subject: [PATCH 095/151] Fix NetDriver to also work well in Linux. --- .../ConsoleDrivers/NetDriver/NetDriver.cs | 12 ++- .../ConsoleDrivers/NetDriver/NetEvents.cs | 90 +++++++++++++++---- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 43 +++++---- 3 files changed, 107 insertions(+), 38 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index b61ed5693..607028811 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -756,7 +756,17 @@ internal class NetDriver : ConsoleDriver _mainLoopDriver._netEvents._forceRead = true; } - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + lock (ansiRequest._responseLock) + { + _mainLoopDriver._waitForProbe.Set (); + _mainLoopDriver._netEvents._waitForStart.Set (); + } + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } } catch (OperationCanceledException) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index d15be6efa..e5a17477f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -1,13 +1,14 @@ #nullable enable -using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui; internal class NetEvents : IDisposable { + private readonly ManualResetEventSlim _inputReady = new (false); private CancellationTokenSource? _inputReadyCancellationTokenSource; - private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); + internal readonly ManualResetEventSlim _waitForStart = new (false); + private readonly Queue _inputQueue = new (); private readonly ConsoleDriver _consoleDriver; private ConsoleKeyInfo []? _cki; private bool _isEscSeq; @@ -30,14 +31,34 @@ internal class NetEvents : IDisposable { while (_inputReadyCancellationTokenSource is { }) { + _waitForStart.Set (); + try { - return _inputQueue.Take (_inputReadyCancellationTokenSource.Token); + if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) + { + if (_inputQueue.Count == 0) + { + _inputReady.Wait (_inputReadyCancellationTokenSource.Token); + } + } + + if (_inputQueue.Count > 0) + { + return _inputQueue.Dequeue (); + } } catch (OperationCanceledException) { return null; } + finally + { + if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + _inputReady.Reset (); + } + } #if PROCESS_REQUEST _neededProcessRequest = false; @@ -102,6 +123,25 @@ internal class NetEvents : IDisposable { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { + try + { + if (!_forceRead) + { + _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return; + } + finally + { + if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + { + _waitForStart.Reset (); + } + } + try { if (_inputQueue.Count == 0 || _forceRead) @@ -125,7 +165,9 @@ internal class NetEvents : IDisposable if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); + AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; } if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) @@ -205,6 +247,8 @@ internal class NetEvents : IDisposable break; } } + + _inputReady.Set (); } catch (OperationCanceledException) { @@ -214,12 +258,12 @@ internal class NetEvents : IDisposable void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) { - _inputQueue.Add ( - new () - { - EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo) - } - ); + _inputQueue.Enqueue ( + new () + { + EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo) + } + ); _isEscSeq = false; } } @@ -265,6 +309,11 @@ internal class NetEvents : IDisposable try { RequestWindowSize (_inputReadyCancellationTokenSource.Token); + + if (_inputQueue.Count > 0) + { + _inputReady.Set (); + } } catch (OperationCanceledException) { @@ -289,12 +338,12 @@ internal class NetEvents : IDisposable int w = Math.Max (winWidth, 0); int h = Math.Max (winHeight, 0); - _inputQueue.Add ( - new () - { - EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (w, h) } - } - ); + _inputQueue.Enqueue ( + new () + { + EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (w, h) } + } + ); return true; } @@ -569,9 +618,9 @@ internal class NetEvents : IDisposable { var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState }; - _inputQueue.Add ( - new () { EventType = EventType.Mouse, MouseEvent = mouseEvent } - ); + _inputQueue.Enqueue ( + new () { EventType = EventType.Mouse, MouseEvent = mouseEvent } + ); } public enum EventType @@ -684,7 +733,7 @@ internal class NetEvents : IDisposable { var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki }; - _inputQueue.Add (inputResult); + _inputQueue.Enqueue (inputResult); } public void Dispose () @@ -693,6 +742,9 @@ internal class NetEvents : IDisposable _inputReadyCancellationTokenSource?.Dispose (); _inputReadyCancellationTokenSource = null; + _inputReady.Dispose (); + _waitForStart.Dispose (); + try { // throws away any typeahead that has been typed by diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 624aec3ce..0b51d2f4d 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Collections.Concurrent; namespace Terminal.Gui; @@ -16,9 +15,10 @@ internal class NetMainLoop : IMainLoopDriver internal Action? ProcessInput; private readonly ManualResetEventSlim _eventReady = new (false); - private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ()); + internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); + private readonly CancellationTokenSource _inputHandlerTokenSource = new (); + private readonly Queue _resultQueue = new (); private MainLoop? _mainLoop; /// Initializes the class with the console driver. @@ -48,6 +48,8 @@ internal class NetMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { + _waitForProbe.Set (); + if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; @@ -60,6 +62,13 @@ internal class NetMainLoop : IMainLoopDriver // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there // are no timers, but there IS an idle handler waiting. _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); + + _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); + + if (!_eventReadyTokenSource.IsCancellationRequested) + { + return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); + } } } catch (OperationCanceledException) @@ -71,13 +80,6 @@ internal class NetMainLoop : IMainLoopDriver _eventReady.Reset (); } - _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); - - if (!_eventReadyTokenSource.IsCancellationRequested) - { - return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); - } - // If cancellation was requested then always return true return true; } @@ -86,10 +88,7 @@ internal class NetMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { - if (_resultQueue.TryTake (out NetEvents.InputResult dequeueResult)) - { - ProcessInput?.Invoke (dequeueResult); - } + ProcessInput?.Invoke (_resultQueue.Dequeue ()); } } @@ -101,8 +100,9 @@ internal class NetMainLoop : IMainLoopDriver _eventReadyTokenSource.Dispose (); _eventReady.Dispose (); + _waitForProbe.Dispose (); - _resultQueue.Dispose(); + _resultQueue.Clear (); _netEvents?.Dispose (); _netEvents = null; @@ -115,9 +115,9 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (_inputHandlerTokenSource.IsCancellationRequested) + if (!_inputHandlerTokenSource.IsCancellationRequested && !_netEvents!._forceRead) { - return; + _waitForProbe.Wait (_inputHandlerTokenSource.Token); } if (_resultQueue?.Count == 0 || _netEvents!._forceRead) @@ -126,7 +126,7 @@ internal class NetMainLoop : IMainLoopDriver if (result.HasValue) { - _resultQueue?.Add (result.Value); + _resultQueue?.Enqueue (result.Value); } } @@ -139,6 +139,13 @@ internal class NetMainLoop : IMainLoopDriver { return; } + finally + { + if (_inputHandlerTokenSource is { IsCancellationRequested: false }) + { + _waitForProbe.Reset (); + } + } } } } From c6512e62de31bd68bf26e763a93a6815be0587e4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 9 Nov 2024 21:29:40 +0000 Subject: [PATCH 096/151] Finally fixed IncompleteCkInfos handling in NetDriver. --- .../ConsoleDrivers/NetDriver/NetEvents.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index e5a17477f..46f0e3a13 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -29,7 +29,7 @@ internal class NetEvents : IDisposable public InputResult? DequeueInput () { - while (_inputReadyCancellationTokenSource is { }) + while (_inputReadyCancellationTokenSource is { Token.IsCancellationRequested: false }) { _waitForStart.Set (); @@ -163,11 +163,20 @@ internal class NetEvents : IDisposable return; } + var ckiAlreadyResized = false; + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) { + ckiAlreadyResized = true; + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; + + if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B') + { + _isEscSeq = true; + } } if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) @@ -205,7 +214,11 @@ internal class NetEvents : IDisposable else { newConsoleKeyInfo = consoleKeyInfo; - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + + if (!ckiAlreadyResized) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + } if (Console.KeyAvailable) { From c8aac60438e4849ae0127ed8e6c6ebd52401fd88 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 9 Nov 2024 21:31:02 +0000 Subject: [PATCH 097/151] Add handling error in the scenario. --- .../Scenarios/AnsiEscapeSequenceRequests.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 07c80f479..dc307ecd8 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -13,11 +13,15 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private readonly object _lockAnswers = new (); private readonly Dictionary _answers = new (); + private readonly object _lockErrors = new (); + private readonly Dictionary _errors = new (); + private GraphView _graphView; private ScatterSeries _sentSeries; private ScatterSeries _answeredSeries; private Label _lblSummary; + private Label _lblErrorSummary; public override void Main () { @@ -151,7 +155,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Y = Pos.Bottom (cbDar), X = Pos.Right (tv), Width = Dim.Fill (), - Height = Dim.Fill (1) + Height = Dim.Fill (2) }; _lblSummary = new () @@ -161,6 +165,13 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Width = Dim.Fill () }; + _lblErrorSummary = new () + { + Y = Pos.Bottom (_lblSummary), + X = Pos.Right (tv), + Width = Dim.Fill () + }; + SetupGraph (); w.Add (lbl); @@ -169,6 +180,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario w.Add (tv); w.Add (_graphView); w.Add (_lblSummary); + w.Add (_lblErrorSummary); return w; } @@ -322,6 +334,21 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return $"Last:{last} U:{unique} T:{total}"; } + private string GetSummaryErrors () + { + if (_errors.Count == 0) + { + return "No errors received yet"; + } + + string last = _errors.Last ().Value; + + int unique = _errors.Values.Distinct ().Count (); + int total = _errors.Count; + + return $"Last:{last} U:{unique} T:{total}"; + } + private void HandleResponse (string response) { lock (_lockAnswers) @@ -330,13 +357,25 @@ public sealed class AnsiEscapeSequenceRequests : Scenario } } + private void HandleResponseError (string response) + { + lock (_lockAnswers) + { + _errors.Add (DateTime.Now, response); + } + } + private void SendDar () { _sends.Add (DateTime.Now); AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; if (Application.Driver!.TryWriteAnsiRequest (ansiRequest)) { - HandleResponse (ansiRequest.AnsiEscapeSequenceResponse.Response); + HandleResponse (ansiRequest.AnsiEscapeSequenceResponse?.Response); + } + else + { + HandleResponseError (ansiRequest.AnsiEscapeSequenceResponse?.Response); } } @@ -383,5 +422,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario { _lblSummary.Text = GetSummary (); _lblSummary.SetNeedsDisplay (); + + _lblErrorSummary.Text = GetSummaryErrors (); + _lblErrorSummary.SetNeedsDisplay (); } } From 1680ec54d65913604fa54322ae1e8b64422301a3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 00:59:55 +0000 Subject: [PATCH 098/151] Make AnsiEscapeSequenceRequests static. --- .../AnsiEscapeSequenceRequestUtils.cs | 8 +- .../AnsiEscapeSequenceRequests.cs | 48 +- .../CursesDriver/CursesDriver.cs | 11 +- .../CursesDriver/UnixMainLoop.cs | 55 +- .../ConsoleDrivers/NetDriver/NetDriver.cs | 18 +- .../ConsoleDrivers/NetDriver/NetEvents.cs | 39 +- .../WindowsDriver/WindowsConsole.cs | 14 +- .../WindowsDriver/WindowsDriver.cs | 15 +- .../WindowsDriver/WindowsMainLoop.cs | 33 +- .../AnsiEscapeSequenceRequestUtilsTests.cs | 699 +++++++++--------- .../Input/AnsiEscapeSequenceRequestsTests.cs | 78 +- 11 files changed, 494 insertions(+), 524 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index fe13359e0..b68a08b27 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -183,7 +183,6 @@ public static class AnsiEscapeSequenceRequestUtils /// /// Decodes an ANSI escape sequence. /// - /// The which may contain a request. /// The which may change. /// The which may change. /// The array. @@ -198,7 +197,6 @@ public static class AnsiEscapeSequenceRequestUtils /// The object. /// The handler that will process the event. public static void DecodeEscSeq ( - AnsiEscapeSequenceRequests? escSeqRequests, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, @@ -346,9 +344,9 @@ public static class AnsiEscapeSequenceRequestUtils return; } - if (escSeqRequests is { } && escSeqRequests.HasResponse (terminator, out seqReqStatus)) + if (AnsiEscapeSequenceRequests.HasResponse (terminator, out seqReqStatus)) { - escSeqRequests.Remove (seqReqStatus); + AnsiEscapeSequenceRequests.Remove (seqReqStatus); return; } @@ -376,7 +374,7 @@ public static class AnsiEscapeSequenceRequestUtils else { // It's request response that wasn't handled by a valid request terminator - System.Diagnostics.Debug.Assert (escSeqRequests is null or { Statuses.Count: > 0 }); + System.Diagnostics.Debug.Assert (AnsiEscapeSequenceRequests.Statuses.Count > 0); InvalidRequestTerminator = ToString (cki); } diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs index e6fffcfc3..62c0908fb 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs @@ -4,35 +4,36 @@ using System.Collections.Concurrent; namespace Terminal.Gui; -// TODO: This class is a singleton. It should use the singleton pattern. /// /// Manages ANSI Escape Sequence requests and responses. The list of /// contains the /// status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator). /// -public class AnsiEscapeSequenceRequests +public static class AnsiEscapeSequenceRequests { /// /// Adds a new request for the ANSI Escape Sequence defined by . Adds a /// instance to list. /// /// The object. - /// The driver in use. - public void Add (AnsiEscapeSequenceRequest ansiRequest, ConsoleDriver? driver = null) + public static void Add (AnsiEscapeSequenceRequest ansiRequest) + { + lock (ansiRequest._responseLock) + { + Statuses.Enqueue (new (ansiRequest)); + } + + System.Diagnostics.Debug.Assert (Statuses.Count > 0); + } + + /// + /// Clear the property. + /// + public static void Clear () { lock (Statuses) { - Statuses.Enqueue (new (ansiRequest)); - - if (driver is null) - { - Console.Out.Write (ansiRequest.Request); - Console.Out.Flush (); - } - else - { - driver.WriteRaw (ansiRequest.Request); - } + Statuses.Clear (); } } @@ -43,22 +44,13 @@ public class AnsiEscapeSequenceRequests /// /// /// if exist, otherwise. - public bool HasResponse (string terminator, out AnsiEscapeSequenceRequestStatus? seqReqStatus) + public static bool HasResponse (string terminator, out AnsiEscapeSequenceRequestStatus? seqReqStatus) { lock (Statuses) { Statuses.TryPeek (out seqReqStatus); - bool result = seqReqStatus?.AnsiRequest.Terminator == terminator; - - if (result) - { - return true; - } - - seqReqStatus = null; - - return false; + return seqReqStatus?.AnsiRequest.Terminator == terminator; } } @@ -70,7 +62,7 @@ public class AnsiEscapeSequenceRequests /// . /// /// The object. - public void Remove (AnsiEscapeSequenceRequestStatus? seqReqStatus) + public static void Remove (AnsiEscapeSequenceRequestStatus? seqReqStatus) { lock (Statuses) { @@ -84,5 +76,5 @@ public class AnsiEscapeSequenceRequests } /// Gets the list. - public ConcurrentQueue Statuses { get; } = new (); + public static ConcurrentQueue Statuses { get; } = new (); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 8b4a7c215..02c239733 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -806,7 +806,9 @@ internal class CursesDriver : ConsoleDriver _waitAnsiResponse.Set (); }; - _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); + AnsiEscapeSequenceRequests.Add (ansiRequest); + + WriteRaw (ansiRequest.Request); _mainLoopDriver._forceRead = true; } @@ -827,15 +829,15 @@ internal class CursesDriver : ConsoleDriver { _mainLoopDriver._forceRead = false; - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { - if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (request.AnsiRequest._responseLock) { // Bad request or no response at all - _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); } } } @@ -848,7 +850,6 @@ internal class CursesDriver : ConsoleDriver /// internal override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); } - } // TODO: One type per file - move to another file diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 203f67770..e9cc52166 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -1,3 +1,4 @@ +#nullable enable // // mainloop.cs: Linux/Curses MainLoop implementation. // @@ -12,7 +13,7 @@ namespace Terminal.Gui; /// In addition to the general functions of the MainLoop, the Unix version can watch file descriptors using the /// AddWatch methods. /// -internal class UnixMainLoop : IMainLoopDriver +internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { /// Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions. [Flags] @@ -37,9 +38,9 @@ internal class UnixMainLoop : IMainLoopDriver PollNval = 32 } - private readonly CursesDriver _cursesDriver; - private MainLoop _mainLoop; - private Pollfd [] _pollMap; + private readonly CursesDriver _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); + private MainLoop? _mainLoop; + private Pollfd []? _pollMap; private readonly ConcurrentQueue _pollDataQueue = new (); private readonly ManualResetEventSlim _eventReady = new (false); internal readonly ManualResetEventSlim _waitForInput = new (false); @@ -47,13 +48,6 @@ internal class UnixMainLoop : IMainLoopDriver private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - public UnixMainLoop (ConsoleDriver consoleDriver = null) - { - _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); - } - - public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); - void IMainLoopDriver.Wakeup () { _eventReady.Set (); } void IMainLoopDriver.Setup (MainLoop mainLoop) @@ -110,7 +104,7 @@ internal class UnixMainLoop : IMainLoopDriver return GetTIOCGWINSZValueInternal (); } - private void EscSeqUtils_ContinuousButtonPressed (object sender, MouseEventArgs e) + private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e) { _pollDataQueue!.Enqueue (EnqueueMouseEvent (e.Flags, e.Position)); } @@ -238,15 +232,15 @@ internal class UnixMainLoop : IMainLoopDriver break; } - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse is { } && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse.Response)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse is { } && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse.Response)) { lock (seqReqStatus!.AnsiRequest._responseLock) { - EscSeqRequests.Statuses.TryDequeue (out _); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); } @@ -299,21 +293,20 @@ internal class UnixMainLoop : IMainLoopDriver ConsoleKeyInfo newConsoleKeyInfo = default; AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - EscSeqRequests, - ref newConsoleKeyInfo, - ref key, - cki, - ref mod, - out string c1Control, - out string code, - out string [] values, - out string terminating, - out bool isMouse, - out List mouseFlags, - out Point pos, - out AnsiEscapeSequenceRequestStatus seqReqStatus, - AnsiEscapeSequenceRequestUtils.ProcessMouseEvent - ); + ref newConsoleKeyInfo, + ref key, + cki, + ref mod, + out string c1Control, + out string code, + out string [] values, + out string terminating, + out bool isMouse, + out List mouseFlags, + out Point pos, + out AnsiEscapeSequenceRequestStatus seqReqStatus, + AnsiEscapeSequenceRequestUtils.ProcessMouseEvent + ); if (isMouse) { @@ -339,7 +332,7 @@ internal class UnixMainLoop : IMainLoopDriver if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) { - if (EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus result)) + if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) { lock (result.AnsiRequest._responseLock) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 607028811..81f9f57e4 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -752,9 +752,9 @@ internal class NetDriver : ConsoleDriver _waitAnsiResponse.Set (); }; - _mainLoopDriver._netEvents!.EscSeqRequests.Add (ansiRequest); + AnsiEscapeSequenceRequests.Add (ansiRequest); - _mainLoopDriver._netEvents._forceRead = true; + _mainLoopDriver._netEvents!._forceRead = true; } if (!_ansiResponseTokenSource.IsCancellationRequested) @@ -763,6 +763,8 @@ internal class NetDriver : ConsoleDriver { _mainLoopDriver._waitForProbe.Set (); _mainLoopDriver._netEvents._waitForStart.Set (); + + WriteRaw (ansiRequest.Request); } _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); @@ -777,15 +779,15 @@ internal class NetDriver : ConsoleDriver { _mainLoopDriver._netEvents._forceRead = false; - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { - if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0 + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (request.AnsiRequest._responseLock) { // Bad request or no response at all - _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); } } } @@ -797,7 +799,11 @@ internal class NetDriver : ConsoleDriver } /// - internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } + internal override void WriteRaw (string ansi) + { + Console.Out.Write (ansi); + Console.Out.Flush (); + } private volatile bool _winSizeChanging; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 46f0e3a13..decbc218c 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -15,8 +15,6 @@ internal class NetEvents : IDisposable #if PROCESS_REQUEST bool _neededProcessRequest; #endif - public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); - public NetEvents (ConsoleDriver consoleDriver) { _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); @@ -79,15 +77,15 @@ internal class NetEvents : IDisposable return Console.ReadKey (intercept); } - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 }) + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) { if (_retries > 1) { - if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (seqReqStatus.AnsiRequest._responseLock) { - EscSeqRequests.Statuses.TryDequeue (out _); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); } @@ -371,21 +369,20 @@ internal class NetEvents : IDisposable { // isMouse is true if it's CSI<, false otherwise AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - EscSeqRequests, - ref newConsoleKeyInfo, - ref key, - cki, - ref mod, - out string c1Control, - out string code, - out string [] values, - out string terminating, - out bool isMouse, - out List mouseFlags, - out Point pos, - out AnsiEscapeSequenceRequestStatus? seqReqStatus, - (f, p) => HandleMouseEvent (MapMouseFlags (f), p) - ); + ref newConsoleKeyInfo, + ref key, + cki, + ref mod, + out string c1Control, + out string code, + out string [] values, + out string terminating, + out bool isMouse, + out List mouseFlags, + out Point pos, + out AnsiEscapeSequenceRequestStatus? seqReqStatus, + (f, p) => HandleMouseEvent (MapMouseFlags (f), p) + ); if (isMouse) { @@ -413,7 +410,7 @@ internal class NetEvents : IDisposable if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) { - if (EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) + if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) { lock (result.AnsiRequest._responseLock) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index de2649747..63229ae09 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -110,10 +110,10 @@ internal class WindowsConsole ansiSequence.Append (inputChar); // Check if the sequence has ended with an expected command terminator - if (_mainLoop?.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus)) + if (AnsiEscapeSequenceRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus)) { // Finished reading the sequence and remove the enqueued request - _mainLoop.EscSeqRequests.Remove (seqReqStatus); + AnsiEscapeSequenceRequests.Remove (seqReqStatus); lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -130,9 +130,9 @@ internal class WindowsConsole } } - if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) + if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus); lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -143,15 +143,15 @@ internal class WindowsConsole _retries = 0; } - else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 }) + else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) { if (_retries > 1) { - if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (seqReqStatus.AnsiRequest._responseLock) { - _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); // Clear the terminator for not be enqueued diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 07d0904ce..d7275e541 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -222,7 +222,9 @@ internal class WindowsDriver : ConsoleDriver _waitAnsiResponse.Set (); }; - _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this); + AnsiEscapeSequenceRequests.Add (ansiRequest); + + WriteRaw (ansiRequest.Request); _mainLoopDriver._forceRead = true; } @@ -238,15 +240,15 @@ internal class WindowsDriver : ConsoleDriver { _mainLoopDriver._forceRead = false; - if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { - if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0 + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (request.AnsiRequest._responseLock) { // Bad request or no response at all - _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _); + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); } } } @@ -257,10 +259,7 @@ internal class WindowsDriver : ConsoleDriver } } - internal override void WriteRaw (string ansi) - { - WinConsole?.WriteANSI (ansi); - } + internal override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } #region Not Implemented diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index fb167075d..ca10a693f 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Collections.Concurrent; namespace Terminal.Gui; @@ -21,7 +20,8 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); // The records that we keep fetching - private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ()); + private readonly Queue _resultQueue = new (); + private readonly ManualResetEventSlim _waitForProbe = new (false); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -38,8 +38,6 @@ internal class WindowsMainLoop : IMainLoopDriver } } - public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new (); - void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; @@ -59,6 +57,7 @@ internal class WindowsMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { + _waitForProbe.Set (); #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif @@ -105,10 +104,7 @@ internal class WindowsMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { - if (_resultQueue.TryTake (out WindowsConsole.InputRecord dequeueResult)) - { - ((WindowsDriver)_consoleDriver).ProcessInput (dequeueResult); - } + ((WindowsDriver)_consoleDriver).ProcessInput (_resultQueue.Dequeue ()); } #if HACK_CHECK_WINCHANGED if (_winChanged) @@ -135,7 +131,9 @@ internal class WindowsMainLoop : IMainLoopDriver } } - _resultQueue.Dispose (); + _waitForProbe?.Dispose (); + + _resultQueue.Clear (); _eventReadyTokenSource.Cancel (); _eventReadyTokenSource.Dispose (); @@ -156,9 +154,9 @@ internal class WindowsMainLoop : IMainLoopDriver { try { - if (_inputHandlerTokenSource.IsCancellationRequested) + if (_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) { - return; + _waitForProbe.Wait (_inputHandlerTokenSource.Token); } if (_resultQueue?.Count == 0 || _forceRead) @@ -167,19 +165,22 @@ internal class WindowsMainLoop : IMainLoopDriver if (result.HasValue) { - _resultQueue!.Add (result.Value); + _resultQueue!.Enqueue (result.Value); } } - if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0) - { - _eventReady.Set (); - } } catch (OperationCanceledException) { return; } + finally + { + if (_inputHandlerTokenSource is { IsCancellationRequested: false }) + { + _eventReady.Set (); + } + } } } diff --git a/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs index c7298d7c4..a9c8f5d05 100644 --- a/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs +++ b/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs @@ -10,7 +10,6 @@ public class AnsiEscapeSequenceRequestUtilsTests private Point _arg2; private string _c1Control, _code, _terminating; private ConsoleKeyInfo [] _cki; - private AnsiEscapeSequenceRequests _escSeqReqProc; private bool _isKeyMouse; [CanBeNull] private AnsiEscapeSequenceRequestStatus _seqReqStatus; @@ -30,22 +29,21 @@ public class AnsiEscapeSequenceRequestUtilsTests var expectedCki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.Escape, _key); Assert.Equal (0, (int)_mod); @@ -65,22 +63,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\u0012', ConsoleKey.R, false, true, true); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.R, _key); Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); @@ -100,22 +97,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('r', ConsoleKey.R, false, true, false); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.R, _key); Assert.Equal (ConsoleModifiers.Alt, _mod); @@ -140,22 +136,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, false, false, false); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (0, (int)_mod); @@ -186,22 +181,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, true, false, false); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift, _mod); @@ -232,22 +226,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, false, true, false); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Alt, _mod); @@ -278,22 +271,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, true, true, false); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, _mod); @@ -324,22 +316,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, false, false, true); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Control, _mod); @@ -370,22 +361,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, true, false, true); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, _mod); @@ -416,22 +406,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, false, true, true); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); @@ -462,22 +451,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = new ('\0', ConsoleKey.F3, true, true, true); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); @@ -511,22 +499,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default (ConsoleKeyInfo); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -561,22 +548,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default (ConsoleKeyInfo); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -616,22 +602,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default (ConsoleKeyInfo); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -664,22 +649,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default (ConsoleKeyInfo); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -719,22 +703,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default (ConsoleKeyInfo); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -788,22 +771,21 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default (ConsoleKeyInfo); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -823,9 +805,9 @@ public class AnsiEscapeSequenceRequestUtilsTests ClearAll (); - Assert.Null (_escSeqReqProc); - _escSeqReqProc = new (); - _escSeqReqProc.Add (new () { Request = "", Terminator = "t" }); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + AnsiEscapeSequenceRequests.Clear (); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); _cki = new ConsoleKeyInfo [] { @@ -841,26 +823,25 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('t', 0, false, false, false) }; expectedCki = default (ConsoleKeyInfo); - Assert.Single (_escSeqReqProc.Statuses); - Assert.Equal ("t", _escSeqReqProc.Statuses.ToArray () [^1].AnsiRequest.Terminator); + Assert.Single (AnsiEscapeSequenceRequests.Statuses); + Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Empty (_escSeqReqProc.Statuses); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -909,22 +890,21 @@ public class AnsiEscapeSequenceRequestUtilsTests var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (consoleKey, _key); @@ -976,22 +956,21 @@ public class AnsiEscapeSequenceRequestUtilsTests ConsoleKeyInfo expectedCki = default; AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.None, _key); Assert.Equal (ConsoleModifiers.None, _mod); @@ -1019,23 +998,25 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default; + // Add a request to avoid assert failure in the DecodeEscSeq method + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.None, _key); @@ -1047,6 +1028,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal ([0], _mouseFlags); Assert.Equal (Point.Empty, _pos); + AnsiEscapeSequenceRequests.HasResponse ("t", out _seqReqStatus); Assert.Null (_seqReqStatus); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -1073,22 +1055,21 @@ public class AnsiEscapeSequenceRequestUtilsTests var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - _escSeqReqProc, - ref _newConsoleKeyInfo, - ref _key, - _cki, - ref _mod, - out _c1Control, - out _code, - out _values, - out _terminating, - out _isKeyMouse, - out _mouseFlags, - out _pos, - out _seqReqStatus, - ProcessContinuousButtonPressed - ); - Assert.Null (_escSeqReqProc); + ref _newConsoleKeyInfo, + ref _key, + _cki, + ref _mod, + out _c1Control, + out _code, + out _values, + out _terminating, + out _isKeyMouse, + out _mouseFlags, + out _pos, + out _seqReqStatus, + ProcessContinuousButtonPressed + ); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (consoleKey, _key); @@ -1559,7 +1540,7 @@ public class AnsiEscapeSequenceRequestUtilsTests private void ClearAll () { - _escSeqReqProc = default (AnsiEscapeSequenceRequests); + AnsiEscapeSequenceRequests.Clear (); _newConsoleKeyInfo = default (ConsoleKeyInfo); _key = default (ConsoleKey); _cki = default (ConsoleKeyInfo []); diff --git a/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs index 0184ab122..aadcc77b7 100644 --- a/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs +++ b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs @@ -5,66 +5,68 @@ public class AnsiEscapeSequenceRequestsTests [Fact] public void Add_Tests () { - var escSeqReq = new AnsiEscapeSequenceRequests (); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); + AnsiEscapeSequenceRequests.Clear (); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + Assert.Single (AnsiEscapeSequenceRequests.Statuses); + Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Equal (2, escSeqReq.Statuses.Count); - Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + Assert.Equal (2, AnsiEscapeSequenceRequests.Statuses.Count); + Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - escSeqReq = new (); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Equal (2, escSeqReq.Statuses.Count); - Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); + AnsiEscapeSequenceRequests.Clear (); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + Assert.Equal (2, AnsiEscapeSequenceRequests.Statuses.Count); + Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.Equal (3, escSeqReq.Statuses.Count); - Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + Assert.Equal (3, AnsiEscapeSequenceRequests.Statuses.Count); + Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); } [Fact] public void Constructor_Defaults () { - var escSeqReq = new AnsiEscapeSequenceRequests (); - Assert.NotNull (escSeqReq.Statuses); - Assert.Empty (escSeqReq.Statuses); + AnsiEscapeSequenceRequests.Clear (); + Assert.NotNull (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); } [Fact] public void Remove_Tests () { - var escSeqReq = new AnsiEscapeSequenceRequests (); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - escSeqReq.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus); - escSeqReq.Remove (seqReqStatus); - Assert.Empty (escSeqReq.Statuses); + AnsiEscapeSequenceRequests.Clear (); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + AnsiEscapeSequenceRequests.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus); + AnsiEscapeSequenceRequests.Remove (seqReqStatus); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - escSeqReq.HasResponse ("t", out seqReqStatus); - escSeqReq.Remove (seqReqStatus); - Assert.Single (escSeqReq.Statuses); - Assert.Equal ("t", escSeqReq.Statuses.ToArray () [^1].AnsiRequest.Terminator); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus); + AnsiEscapeSequenceRequests.Remove (seqReqStatus); + Assert.Single (AnsiEscapeSequenceRequests.Statuses); + Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - escSeqReq.HasResponse ("t", out seqReqStatus); - escSeqReq.Remove (seqReqStatus); - Assert.Empty (escSeqReq.Statuses); + AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus); + AnsiEscapeSequenceRequests.Remove (seqReqStatus); + Assert.Empty (AnsiEscapeSequenceRequests.Statuses); } [Fact] public void Requested_Tests () { - var escSeqReq = new AnsiEscapeSequenceRequests (); - Assert.False (escSeqReq.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus)); + AnsiEscapeSequenceRequests.Clear (); + Assert.False (AnsiEscapeSequenceRequests.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus)); Assert.Null (seqReqStatus); - escSeqReq.Add (new () { Request = "", Terminator = "t" }); - Assert.False (escSeqReq.HasResponse ("r", out seqReqStatus)); - Assert.Null (seqReqStatus); - Assert.True (escSeqReq.HasResponse ("t", out seqReqStatus)); + AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + Assert.False (AnsiEscapeSequenceRequests.HasResponse ("r", out seqReqStatus)); Assert.NotNull (seqReqStatus); + Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator); + Assert.True (AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus)); + Assert.NotNull (seqReqStatus); + Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator); } } From 6444cc07dd20d4f758758ea2e04ff2005f39042c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 12:16:40 +0000 Subject: [PATCH 099/151] Fix ansi responses not being handling in WSL. --- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index decbc218c..e8fb25996 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -77,11 +77,16 @@ internal class NetEvents : IDisposable return Console.ReadKey (intercept); } - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) + // The delay must be here because it may have a request response after a while + // In WSL it takes longer for keys to be available. + Task.Delay (100, cancellationToken).Wait (cancellationToken); + + if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) { if (_retries > 1) { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) + && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) { lock (seqReqStatus.AnsiRequest._responseLock) { @@ -102,11 +107,6 @@ internal class NetEvents : IDisposable { _retries = 0; } - - if (!_forceRead) - { - Task.Delay (100, cancellationToken).Wait (cancellationToken); - } } cancellationToken.ThrowIfCancellationRequested (); From 88f40453810f5cc9e725612a6b99d01715725ad4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 16:38:21 +0000 Subject: [PATCH 100/151] Fix merge errors. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 113 ++---------- .../CursesDriver/CursesDriver.cs | 172 +++--------------- .../ConsoleDrivers/NetDriver/NetDriver.cs | 64 +++---- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 11 +- .../WindowsDriver/WindowsConsole.cs | 42 ++--- .../WindowsDriver/WindowsDriver.cs | 33 ++-- .../Scenarios/AnsiEscapeSequenceRequests.cs | 6 +- 7 files changed, 117 insertions(+), 324 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 8d0cd9314..e9e2af5ce 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -30,10 +30,6 @@ public abstract class ConsoleDriver /// public virtual string GetVersionInfo () { return GetType ().Name; } - /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. - /// This is only implemented in . - public abstract void Suspend (); - #region ANSI Esc Sequence Handling // QUESTION: Should this be virtual with a default implementation that does the common stuff? @@ -94,9 +90,6 @@ public abstract class ConsoleDriver } } - /// Updates the screen to reflect all the changes that have been done to the display buffer - public abstract void Refresh (); - /// /// Gets the column last set by . and are used by /// and to determine where to add content. @@ -181,28 +174,6 @@ public abstract class ConsoleDriver /// The topmost row in the terminal. internal virtual int Top { get; set; } = 0; - private Rectangle _clip; - - /// - /// Gets or sets the clip rectangle that and are subject - /// to. - /// - /// The rectangle describing the of region. - public Rectangle Clip - { - get => _clip; - set - { - if (_clip == value) - { - return; - } - - // Don't ever let Clip be bigger than Screen - _clip = Rectangle.Intersect (Screen, value); - } - } - /// Adds the specified rune to the display at the current cursor position. /// /// @@ -420,19 +391,23 @@ public abstract class ConsoleDriver /// /// The Screen-relative rectangle. /// The Rune used to fill the rectangle - public void FillRect (Rectangle rect, Rune rune = default) + internal void FillRect (Rectangle rect, Rune rune = default) { - rect = Rectangle.Intersect (rect, Clip); - + // BUGBUG: This should be a method on Region + rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen); lock (Contents!) { for (int r = rect.Y; r < rect.Y + rect.Height; r++) { for (int c = rect.X; c < rect.X + rect.Width; c++) { - Contents [r, c] = new () + if (!IsValidLocation (rune, c, r)) { - Rune = rune != default (Rune) ? rune : (Rune)' ', + continue; + } + Contents [r, c] = new Cell + { + Rune = (rune != default ? rune : (Rune)' '), Attribute = CurrentAttribute, IsDirty = true }; _dirtyLines! [r] = true; @@ -498,41 +473,6 @@ public abstract class ConsoleDriver } } - /// Determines if the terminal cursor should be visible or not and sets it accordingly. - /// upon success - public abstract bool EnsureCursorVisibility (); - - /// 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 - internal void FillRect (Rectangle rect, Rune rune = default) - { - // BUGBUG: This should be a method on Region - rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen); - lock (Contents!) - { - for (int r = rect.Y; r < rect.Y + rect.Height; r++) - { - for (int c = rect.X; c < rect.X + rect.Width; c++) - { - if (!IsValidLocation (rune, c, r)) - { - continue; - } - Contents [r, c] = new Cell - { - Rune = (rune != default ? rune : (Rune)' '), - Attribute = CurrentAttribute, IsDirty = true - }; - _dirtyLines! [r] = true; - } - } - } - } - /// /// Fills the specified rectangle with the specified . This method is a convenience method /// that calls . @@ -554,17 +494,6 @@ public abstract class ConsoleDriver /// upon success public abstract bool GetCursorVisibility (out CursorVisibility visibility); - /// Sets the position of the terminal cursor to and . - public abstract void UpdateCursor (); - - /// 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 virtual 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. @@ -586,27 +515,6 @@ public abstract class ConsoleDriver } } - // TODO: Make internal once Menu is upgraded - /// - /// 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 virtual void Move (int col, int row) - { - //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0)); - Col = col; - Row = row; - } - /// Called when the terminal size changes. Fires the event. /// internal void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); } @@ -630,6 +538,9 @@ public abstract class ConsoleDriver /// upon success public abstract bool SetCursorVisibility (CursorVisibility visibility); + /// The event fired when the terminal is resized. + public event EventHandler? SizeChanged; + #endregion Cursor Handling /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 12d6eacbe..7a570c739 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -70,22 +70,22 @@ internal class CursesDriver : ConsoleDriver if (consoleKey == ConsoleKey.Packet) { - var mod = new ConsoleModifiers (); + //var mod = new ConsoleModifiers (); - if (shift) - { - mod |= ConsoleModifiers.Shift; - } + //if (shift) + //{ + // mod |= ConsoleModifiers.Shift; + //} - if (alt) - { - mod |= ConsoleModifiers.Alt; - } + //if (alt) + //{ + // mod |= ConsoleModifiers.Alt; + //} - if (control) - { - mod |= ConsoleModifiers.Control; - } + //if (control) + //{ + // mod |= ConsoleModifiers.Control; + //} var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); @@ -96,8 +96,8 @@ internal class CursesDriver : ConsoleDriver key = (KeyCode)keyChar; } - OnKeyDown (new Key (key)); - OnKeyUp (new Key (key)); + OnKeyDown (new (key)); + OnKeyUp (new (key)); //OnKeyPressed (new KeyEventArgsEventArgs (key)); } @@ -118,10 +118,10 @@ internal class CursesDriver : ConsoleDriver if (visibility != CursorVisibility.Invisible) { Console.Out.Write ( - EscSeqUtils.CSI_SetCursorStyle ( - (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) - & 0xFF) - ) + AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( + (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)(((int)visibility >> 24) + & 0xFF) + ) ); } @@ -134,7 +134,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); } } @@ -142,7 +142,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); } } @@ -170,14 +170,18 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) { - Curses.move (Row, Col); - if (Force16Colors) { + Curses.move (Row, Col); + Curses.raw (); Curses.noecho (); Curses.refresh (); } + else + { + _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + } } } @@ -399,8 +403,6 @@ internal class CursesDriver : ConsoleDriver return updated; } - #endregion Screen and Contents - #region Color Handling public override bool SupportsTrueColor => true; @@ -531,8 +533,6 @@ internal class CursesDriver : ConsoleDriver #endregion - #region Cursor Support - private CursorVisibility? _currentCursorVisibility; private CursorVisibility? _initialCursorVisibility; @@ -568,118 +568,6 @@ internal class CursesDriver : ConsoleDriver return true; } - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - if (_initialCursorVisibility.HasValue == false) - { - return false; - } - - if (!RunningUnitTests) - { - Curses.curs_set (((int)visibility >> 16) & 0x000000FF); - } - - if (visibility != CursorVisibility.Invisible) - { - Console.Out.Write ( - AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( - (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)(((int)visibility >> 24) - & 0xFF) - ) - ); - } - - _currentCursorVisibility = visibility; - - return true; - } - - public override void UpdateCursor () - { - EnsureCursorVisibility (); - - if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) - { - if (Force16Colors) - { - Curses.move (Row, Col); - - Curses.raw (); - Curses.noecho (); - Curses.refresh (); - } - else - { - _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); - } - } - } - - #endregion Cursor Support - - #region Keyboard Support - - public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) - { - KeyCode key; - - if (consoleKey == ConsoleKey.Packet) - { - //var mod = new ConsoleModifiers (); - - //if (shift) - //{ - // mod |= ConsoleModifiers.Shift; - //} - - //if (alt) - //{ - // mod |= ConsoleModifiers.Alt; - //} - - //if (control) - //{ - // mod |= ConsoleModifiers.Control; - //} - - var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); - key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo); - } - else - { - key = (KeyCode)keyChar; - } - - OnKeyDown (new (key)); - OnKeyUp (new (key)); - - //OnKeyPressed (new KeyEventArgsEventArgs (key)); - } - - #endregion Keyboard Support - - #region Mouse Support - public void StartReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); - } - } - - public void StopReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); - } - } - - #endregion Mouse Support - private bool SetCursorPosition (int col, int row) { // + 1 is needed because non-Windows is based on 1 instead of 0 and @@ -875,8 +763,6 @@ internal class CursesDriver : ConsoleDriver return false; } - #region Low-Level Unix Stuff - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); private CancellationTokenSource? _ansiResponseTokenSource; @@ -1028,6 +914,4 @@ internal static class Platform [DllImport ("libc")] private static extern int uname (nint buf); -} - -#endregion Low-Level Unix Stuff +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 81f9f57e4..7aa943310 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -14,12 +14,6 @@ internal class NetDriver : ConsoleDriver public bool IsWinPlatform { get; private set; } public NetWinVTConsole? NetWinConsole { get; private set; } - public override void Refresh () - { - UpdateScreen (); - UpdateCursor (); - } - public override void Suspend () { if (Environment.OSVersion.Platform != PlatformID.Unix) @@ -52,17 +46,16 @@ internal class NetDriver : ConsoleDriver StartReportingMouseMoves (); } - #region Screen and Contents - - public override void UpdateScreen () + public override bool UpdateScreen () { + bool updated = false; if (RunningUnitTests || _winSizeChanging || Console.WindowHeight < 1 || Contents?.Length != Rows * Cols || Rows != Console.WindowHeight) { - return; + return updated; } var top = 0; @@ -80,7 +73,7 @@ internal class NetDriver : ConsoleDriver { if (Console.WindowHeight < 1) { - return; + return updated; } if (!_dirtyLines! [row]) @@ -90,9 +83,10 @@ internal class NetDriver : ConsoleDriver if (!SetCursorPosition (0, row)) { - return; + return updated; } + updated = true; _dirtyLines [row] = false; output.Clear (); @@ -138,30 +132,33 @@ internal class NetDriver : ConsoleDriver { output.Append ( AnsiEscapeSequenceRequestUtils.CSI_SetGraphicsRendition ( - MapColors ( - (ConsoleColor)attr.Background.GetClosestNamedColor16 (), - false - ), - MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ()) - ) + MapColors ( + (ConsoleColor)attr.Background + .GetClosestNamedColor16 (), + false + ), + MapColors ( + (ConsoleColor)attr.Foreground + .GetClosestNamedColor16 ()) + ) ); } else { output.Append ( AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB ( - attr.Foreground.R, - attr.Foreground.G, - attr.Foreground.B - ) + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ) ); output.Append ( AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB ( - attr.Background.R, - attr.Background.G, - attr.Background.B - ) + attr.Background.R, + attr.Background.G, + attr.Background.B + ) ); } } @@ -199,7 +196,7 @@ internal class NetDriver : ConsoleDriver Console.Write (output); } - foreach (SixelToRender s in Application.Sixel) + foreach (var s in Application.Sixel) { if (!string.IsNullOrWhiteSpace (s.SixelData)) { @@ -221,9 +218,9 @@ internal class NetDriver : ConsoleDriver lastCol += outputWidth; outputWidth = 0; } - } - #endregion Screen and Contents + return updated; + } #region Init/End/MainLoop @@ -821,7 +818,7 @@ internal class NetDriver : ConsoleDriver } } - private void ResizeScreen () + public virtual void ResizeScreen () { // Not supported on Unix. if (IsWinPlatform) @@ -846,18 +843,17 @@ internal class NetDriver : ConsoleDriver } #pragma warning restore CA1416 } - // INTENT: Why are these eating the exceptions? // Comments would be good here. catch (IOException) { // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); } catch (ArgumentOutOfRangeException) { // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); } } else @@ -866,7 +862,7 @@ internal class NetDriver : ConsoleDriver } // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); } #endregion Low-Level DotNet tuff diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 0b51d2f4d..36490b32f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -29,19 +29,20 @@ internal class NetMainLoop : IMainLoopDriver { ArgumentNullException.ThrowIfNull (consoleDriver); - _netEvents = new (consoleDriver); + if (!ConsoleDriver.RunningUnitTests) + { + _netEvents = new (consoleDriver); + } } void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; - if (ConsoleDriver.RunningUnitTests) + if (!ConsoleDriver.RunningUnitTests) { - return; + Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } - - Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } void IMainLoopDriver.Wakeup () { _eventReady.Set (); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 63229ae09..50480b153 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -517,33 +517,33 @@ internal class WindowsConsole _inputReadyCancellationTokenSource = null; } - //internal Size GetConsoleBufferWindow (out Point position) - //{ - // if (_screenBuffer == nint.Zero) - // { - // position = Point.Empty; + internal Size GetConsoleBufferWindow (out Point position) + { + if (_outputHandle == nint.Zero) + { + position = Point.Empty; - // return Size.Empty; - // } + return Size.Empty; + } - // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - // csbi.cbSize = (uint)Marshal.SizeOf (csbi); + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); - // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - // position = Point.Empty; + if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + { + //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + position = Point.Empty; - // return Size.Empty; - // } + return Size.Empty; + } - // Size sz = new ( - // csbi.srWindow.Right - csbi.srWindow.Left + 1, - // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - // position = new (csbi.srWindow.Left, csbi.srWindow.Top); + Size sz = new ( + csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new (csbi.srWindow.Left, csbi.srWindow.Top); - // return sz; - //} + return sz; + } internal Size GetConsoleOutputWindow (out Point position) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index d7275e541..34afbc056 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -117,16 +117,6 @@ internal class WindowsDriver : ConsoleDriver public override bool IsRuneSupported (Rune rune) { return base.IsRuneSupported (rune) && rune.IsBmp; } - public override void Refresh () - { - if (!RunningUnitTests) - { - UpdateScreen (); - //WinConsole?.SetInitialCursorVisibility (); - UpdateCursor (); - } - } - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) { var input = new WindowsConsole.InputRecord @@ -289,6 +279,11 @@ internal class WindowsDriver : ConsoleDriver public override void UpdateCursor () { + if (RunningUnitTests) + { + return; + } + if (Col < 0 || Row < 0 || Col >= Cols || Row >= Rows) { GetCursorVisibility (out CursorVisibility cursorVisibility); @@ -382,13 +377,14 @@ internal class WindowsDriver : ConsoleDriver #endregion Cursor Handling - public override void UpdateScreen () + public override bool UpdateScreen () { - Size windowSize = WinConsole?.GetConsoleOutputWindow (out Point _) ?? new Size (Cols, Rows); + bool updated = false; + Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows); if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) { - return; + return updated; } var bufferCoords = new WindowsConsole.Coord @@ -405,6 +401,7 @@ internal class WindowsDriver : ConsoleDriver } _dirtyLines [row] = false; + updated = true; for (var col = 0; col < Cols; col++) { @@ -463,6 +460,8 @@ internal class WindowsDriver : ConsoleDriver } WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); + + return updated; } internal override void End () @@ -502,6 +501,7 @@ internal class WindowsDriver : ConsoleDriver Size winSize = WinConsole.GetConsoleOutputWindow (out Point _); Cols = winSize.Width; Rows = winSize.Height; + OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); } WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); @@ -524,7 +524,7 @@ internal class WindowsDriver : ConsoleDriver _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); _damageRegion = new WindowsConsole.SmallRect { @@ -613,11 +613,12 @@ internal class WindowsDriver : ConsoleDriver Cols = inputEvent.WindowBufferSizeEvent._size.X; Rows = inputEvent.WindowBufferSizeEvent._size.Y; + Application.Screen = new (0, 0, Cols, Rows); ResizeScreen (); ClearContents (); Application.Top?.SetNeedsLayout (); - Application.Refresh (); + Application.LayoutAndDraw (); break; #endif @@ -961,7 +962,7 @@ internal class WindowsDriver : ConsoleDriver { _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); _damageRegion = new WindowsConsole.SmallRect { diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index dc307ecd8..f281dc461 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -414,16 +414,16 @@ public sealed class AnsiEscapeSequenceRequests : Scenario // _graphView.ScrollOffset = new PointF(,0); if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) { - _graphView.SetNeedsDisplay (); + _graphView.SetNeedsDraw (); } } private void UpdateResponses () { _lblSummary.Text = GetSummary (); - _lblSummary.SetNeedsDisplay (); + _lblSummary.SetNeedsDraw (); _lblErrorSummary.Text = GetSummaryErrors (); - _lblErrorSummary.SetNeedsDisplay (); + _lblErrorSummary.SetNeedsDraw (); } } From 71fb38db9bffd01dca93f1d1f5804d69a5ecd0b1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 16:39:10 +0000 Subject: [PATCH 101/151] Add unit test for Screen. --- UnitTests/Application/ApplicationTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index c0289893e..e30d4c55e 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -579,6 +579,25 @@ public class ApplicationTests } } + [Fact] + public void Screen_Size_Changes () + { + var driver = new FakeDriver (); + Application.Init (driver); + Assert.Equal (new (0, 0, 80, 25), driver.Screen); + Assert.Equal (new (0, 0, 80, 25), Application.Screen); + + driver.Cols = 100; + driver.Rows = 30; + // ConsoleDriver.Screen isn't assignable + //driver.Screen = new (0, 0, driver.Cols, Rows); + Assert.Equal (new (0, 0, 100, 30), driver.Screen); + Assert.NotEqual (new (0, 0, 100, 30), Application.Screen); + Assert.Equal (new (0, 0, 80, 25), Application.Screen); + Application.Screen = new (0, 0, driver.Cols, driver.Rows); + Assert.Equal (new (0, 0, 100, 30), driver.Screen); + } + private void Init () { Application.Init (new FakeDriver ()); From 0f6ce06dd1a893ea2596f181e87834839f2e8004 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 18:51:59 +0000 Subject: [PATCH 102/151] Fix SetCursorVisibility in CursesDriver. --- .../CursesDriver/CursesDriver.cs | 57 ++++++++++--------- .../CursesDriver/UnixMainLoop.cs | 23 +++----- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 7a570c739..2f56f44be 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -102,34 +102,6 @@ internal class CursesDriver : ConsoleDriver //OnKeyPressed (new KeyEventArgsEventArgs (key)); } - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - if (_initialCursorVisibility.HasValue == false) - { - return false; - } - - if (!RunningUnitTests) - { - Curses.curs_set (((int)visibility >> 16) & 0x000000FF); - } - - if (visibility != CursorVisibility.Invisible) - { - Console.Out.Write ( - AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( - (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)(((int)visibility >> 24) - & 0xFF) - ) - ); - } - - _currentCursorVisibility = visibility; - - return true; - } - public void StartReportingMouseMoves () { if (!RunningUnitTests) @@ -568,6 +540,35 @@ internal class CursesDriver : ConsoleDriver return true; } + /// + public override bool SetCursorVisibility (CursorVisibility visibility) + { + if (_initialCursorVisibility.HasValue == false) + { + return false; + } + + if (!RunningUnitTests) + { + Curses.curs_set (((int)visibility >> 16) & 0x000000FF); + } + + if (visibility != CursorVisibility.Invisible) + { + _mainLoopDriver?.WriteRaw ( + AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( + (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style) + (((int)visibility >> 24) + & 0xFF) + ) + ); + } + + _currentCursorVisibility = visibility; + + return true; + } + private bool SetCursorPosition (int col, int row) { // + 1 is needed because non-Windows is based on 1 instead of 0 and diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index e9cc52166..02e0fb8e7 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -182,6 +182,15 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { while (!_inputHandlerTokenSource.IsCancellationRequested) { + try + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + int n = poll (_pollMap, (uint)_pollMap.Length, 0); if (n > 0) @@ -257,18 +266,6 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { _retries = 0; } - - try - { - if (!_forceRead) - { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return; - } } } @@ -437,8 +434,6 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { // Write to stdout (fd 1) write (STDOUT_FILENO, ansiRequest, ansiRequest.Length); - - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); } [DllImport ("libc")] From f51fbbe5ed5ee2f062438e4939d0a2f0191668f7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 19:01:43 +0000 Subject: [PATCH 103/151] Forgot Shutdown in unit test. --- UnitTests/Application/ApplicationTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index e30d4c55e..1bd06b30c 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -596,6 +596,8 @@ public class ApplicationTests Assert.Equal (new (0, 0, 80, 25), Application.Screen); Application.Screen = new (0, 0, driver.Cols, driver.Rows); Assert.Equal (new (0, 0, 100, 30), driver.Screen); + + Application.Shutdown (); } private void Init () From 089f7569fd6584a2b7630e4db61fd5cf62f91921 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 10 Nov 2024 19:59:34 +0000 Subject: [PATCH 104/151] Simplify request responses handling. --- .../AnsiEscapeSequenceRequestUtils.cs | 20 ++++++++---- .../CursesDriver/UnixMainLoop.cs | 31 ++---------------- .../ConsoleDrivers/NetDriver/NetEvents.cs | 32 +++---------------- 3 files changed, 21 insertions(+), 62 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index b68a08b27..bc2ff7232 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -175,11 +175,6 @@ public static class AnsiEscapeSequenceRequestUtils /// public static ConsoleKeyInfo []? IncompleteCkInfos { get; set; } - /// - /// Represent a response that was requested by an invalid terminator. - /// - public static string? InvalidRequestTerminator { get; set; } - /// /// Decodes an ANSI escape sequence. /// @@ -348,6 +343,13 @@ public static class AnsiEscapeSequenceRequestUtils { AnsiEscapeSequenceRequests.Remove (seqReqStatus); + var ckiString = ToString (cki); + + lock (seqReqStatus?.AnsiRequest._responseLock!) + { + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString); + } + return; } @@ -376,7 +378,13 @@ public static class AnsiEscapeSequenceRequestUtils // It's request response that wasn't handled by a valid request terminator System.Diagnostics.Debug.Assert (AnsiEscapeSequenceRequests.Statuses.Count > 0); - InvalidRequestTerminator = ToString (cki); + if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) + { + lock (result.AnsiRequest._responseLock) + { + result.AnsiRequest.RaiseResponseFromInput (ToString (cki)); + } + } } } else diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 02e0fb8e7..c7134e96e 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -245,7 +245,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { if (_retries > 1) { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse is { } && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse.Response)) + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus)) { lock (seqReqStatus!.AnsiRequest._responseLock) { @@ -301,7 +301,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver out bool isMouse, out List mouseFlags, out Point pos, - out AnsiEscapeSequenceRequestStatus seqReqStatus, + out AnsiEscapeSequenceRequestStatus? seqReqStatus, AnsiEscapeSequenceRequestUtils.ProcessMouseEvent ); @@ -315,33 +315,6 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver return; } - if (seqReqStatus is { }) - { - var ckiString = AnsiEscapeSequenceRequestUtils.ToString (cki); - - lock (seqReqStatus.AnsiRequest._responseLock) - { - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString); - } - - return; - } - - if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) - { - if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) - { - lock (result.AnsiRequest._responseLock) - { - result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); - - AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null; - } - } - - return; - } - if (newConsoleKeyInfo != default) { _pollDataQueue!.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo)); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index e8fb25996..76258758a 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -394,34 +394,12 @@ internal class NetEvents : IDisposable return; } - if (seqReqStatus is { }) - { - //HandleRequestResponseEvent (c1Control, code, values, terminating); + //if (seqReqStatus is { }) + //{ + // HandleRequestResponseEvent (c1Control, code, values, terminating); - var ckiString = AnsiEscapeSequenceRequestUtils.ToString (cki); - - lock (seqReqStatus.AnsiRequest._responseLock) - { - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString); - } - - return; - } - - if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator)) - { - if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) - { - lock (result.AnsiRequest._responseLock) - { - result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator); - - AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null; - } - } - - return; - } + // return; + //} if (newConsoleKeyInfo != default) { From 36876b989900f6531e06ba89b3420777f37e0819 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 01:10:18 +0000 Subject: [PATCH 105/151] Change TryWriteAnsiRequest method to virtual which use common code for all drivers. --- Terminal.Gui/Application/Application.Run.cs | 3 +- Terminal.Gui/Application/MainLoop.cs | 10 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 69 ++++- .../CursesDriver/CursesDriver.cs | 70 ----- .../CursesDriver/UnixMainLoop.cs | 16 +- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 2 +- .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 2 + .../ConsoleDrivers/NetDriver/NetDriver.cs | 77 ----- .../ConsoleDrivers/NetDriver/NetEvents.cs | 282 +++++++++--------- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 6 +- .../WindowsDriver/WindowsDriver.cs | 62 ---- .../WindowsDriver/WindowsMainLoop.cs | 10 +- .../Scenarios/AnsiEscapeSequenceRequests.cs | 5 +- UnitTests/Application/MainLoopTests.cs | 4 + 14 files changed, 238 insertions(+), 380 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 898fc935b..7414054b5 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -1,7 +1,6 @@ #nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; namespace Terminal.Gui; @@ -522,7 +521,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// The driver for the application /// The main loop. - internal static MainLoop? MainLoop { get; private set; } + public static MainLoop? MainLoop { get; private set; } /// /// Set to true to cause to be called after the first iteration. Set to false (the default) to diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs index ee4bba220..d3cce5efe 100644 --- a/Terminal.Gui/Application/MainLoop.cs +++ b/Terminal.Gui/Application/MainLoop.cs @@ -10,7 +10,7 @@ using System.Collections.ObjectModel; namespace Terminal.Gui; /// Interface to create a platform specific driver. -internal interface IMainLoopDriver +public interface IMainLoopDriver { /// Must report whether there are any events pending, or even block waiting for events. /// true, if there were pending events, false otherwise. @@ -29,6 +29,10 @@ internal interface IMainLoopDriver /// Wakes up the that might be waiting on input, must be thread safe. void Wakeup (); + + bool _forceRead { get; set; } + + ManualResetEventSlim _waitForInput { get; set; } } /// The MainLoop monitors timers and idle handlers. @@ -36,7 +40,7 @@ internal interface IMainLoopDriver /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. /// -internal class MainLoop : IDisposable +public class MainLoop : IDisposable { internal List> _idleHandlers = new (); internal SortedList _timeouts = new (); @@ -72,7 +76,7 @@ internal class MainLoop : IDisposable /// The current in use. /// The main loop driver. - internal IMainLoopDriver MainLoopDriver { get; private set; } + public IMainLoopDriver MainLoopDriver { get; private set; } /// Used for unit tests. internal bool Running { get; set; } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e9e2af5ce..6caf6948f 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -32,6 +32,9 @@ public abstract class ConsoleDriver #region ANSI Esc Sequence Handling + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private CancellationTokenSource? _ansiResponseTokenSource; + // QUESTION: Should this be virtual with a default implementation that does the common stuff? // QUESTION: Looking at the implementations of this method, there is TONs of duplicated code. // QUESTION: We should figure out how to find just the things that are unique to each driver and @@ -39,9 +42,73 @@ public abstract class ConsoleDriver /// /// Provide handling for the terminal write ANSI escape sequence request. /// + /// The object. /// The object. /// The request response. - public abstract bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); + public virtual bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) + { + if (mainLoopDriver is null) + { + return false; + } + + _ansiResponseTokenSource ??= new (); + + try + { + lock (ansiRequest._responseLock) + { + AnsiEscapeSequenceRequest? request = ansiRequest; + + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == request); + Debug.Assert (e == request.AnsiEscapeSequenceResponse); + + _waitAnsiResponse.Set (); + }; + + AnsiEscapeSequenceRequests.Add (ansiRequest); + + WriteRaw (ansiRequest.Request); + + mainLoopDriver._forceRead = true; + } + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + mainLoopDriver._waitForInput.Set (); + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return false; + } + + lock (ansiRequest._responseLock) + { + mainLoopDriver._forceRead = false; + + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + { + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (request.AnsiRequest._responseLock) + { + // Bad request or no response at all + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + } + } + } + + _waitAnsiResponse.Reset (); + + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; + } + } // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API? diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 2f56f44be..84c74fa20 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -727,9 +727,6 @@ internal class CursesDriver : ConsoleDriver internal override void End () { - _ansiResponseTokenSource?.Cancel (); - _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse.Dispose (); StopReportingMouseMoves (); SetCursorVisibility (CursorVisibility.Default); @@ -765,73 +762,6 @@ internal class CursesDriver : ConsoleDriver return false; } - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (_mainLoopDriver is null) - { - return false; - } - - _ansiResponseTokenSource ??= new (); - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - _mainLoopDriver._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - _mainLoopDriver._waitForInput.Set (); - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return false; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - /// internal override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); } } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index c7134e96e..84fa67ad3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -43,7 +43,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver private Pollfd []? _pollMap; private readonly ConcurrentQueue _pollDataQueue = new (); private readonly ManualResetEventSlim _eventReady = new (false); - internal readonly ManualResetEventSlim _waitForInput = new (false); + ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); private readonly ManualResetEventSlim _windowSizeChange = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -152,7 +152,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver } } - internal bool _forceRead; + bool IMainLoopDriver._forceRead { get; set; } private int _retries; private void CursesInputHandler () @@ -161,9 +161,9 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) { - _waitForInput.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } } catch (OperationCanceledException) @@ -174,11 +174,11 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { if (!_inputHandlerTokenSource.IsCancellationRequested) { - _waitForInput.Reset (); + ((IMainLoopDriver)this)._waitForInput.Reset (); } } - if (_pollDataQueue?.Count == 0 || _forceRead) + if (_pollDataQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) { while (!_inputHandlerTokenSource.IsCancellationRequested) { @@ -340,7 +340,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForInput.Set (); + ((IMainLoopDriver)this)._waitForInput.Set (); _windowSizeChange.Set (); if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) @@ -390,7 +390,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver _inputHandlerTokenSource?.Cancel (); _inputHandlerTokenSource?.Dispose (); - _waitForInput?.Dispose (); + ((IMainLoopDriver)this)._waitForInput?.Dispose (); _windowSizeChange.Dispose(); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 4ebb4029e..64a3415da 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -393,7 +393,7 @@ public class FakeDriver : ConsoleDriver } /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } + public override bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } /// internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index d90caace7..16a1a76b7 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -3,6 +3,8 @@ internal class FakeMainLoop : IMainLoopDriver { public Action MockKeyPressed; + public bool _forceRead { get; set; } + public ManualResetEventSlim _waitForInput { get; set; } = new (); public FakeMainLoop (ConsoleDriver consoleDriver = null) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 7aa943310..f66d921bf 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -359,10 +359,7 @@ internal class NetDriver : ConsoleDriver StopReportingMouseMoves (); - _ansiResponseTokenSource?.Cancel (); - _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse.Dispose (); if (!RunningUnitTests) { @@ -721,80 +718,6 @@ internal class NetDriver : ConsoleDriver #region Low-Level DotNet tuff - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - lock (ansiRequest._responseLock) - { - if (_mainLoopDriver is null) - { - return false; - } - } - - _ansiResponseTokenSource ??= new (); - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - _mainLoopDriver._netEvents!._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - lock (ansiRequest._responseLock) - { - _mainLoopDriver._waitForProbe.Set (); - _mainLoopDriver._netEvents._waitForStart.Set (); - - WriteRaw (ansiRequest.Request); - } - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return false; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._netEvents._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - /// internal override void WriteRaw (string ansi) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 76258758a..eb4ecf0d3 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -7,7 +7,6 @@ internal class NetEvents : IDisposable { private readonly ManualResetEventSlim _inputReady = new (false); private CancellationTokenSource? _inputReadyCancellationTokenSource; - internal readonly ManualResetEventSlim _waitForStart = new (false); private readonly Queue _inputQueue = new (); private readonly ConsoleDriver _consoleDriver; private ConsoleKeyInfo []? _cki; @@ -29,8 +28,6 @@ internal class NetEvents : IDisposable { while (_inputReadyCancellationTokenSource is { Token.IsCancellationRequested: false }) { - _waitForStart.Set (); - try { if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) @@ -81,32 +78,7 @@ internal class NetEvents : IDisposable // In WSL it takes longer for keys to be available. Task.Delay (100, cancellationToken).Wait (cancellationToken); - if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - if (_retries > 1) - { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) - && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (seqReqStatus.AnsiRequest._responseLock) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } + ProcessResponse (); } cancellationToken.ThrowIfCancellationRequested (); @@ -114,152 +86,169 @@ internal class NetEvents : IDisposable return default (ConsoleKeyInfo); } - internal bool _forceRead; + //internal bool _forceRead; private int _retries; + private void ProcessResponse () + { + if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) + { + if (_retries > 1) + { + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) + && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (seqReqStatus.AnsiRequest._responseLock) + { + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + } + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + } + private void ProcessInputQueue () { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { try { - if (!_forceRead) - { - _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return; - } - finally - { - if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) - { - _waitForStart.Reset (); - } - } + ConsoleKey key = 0; + ConsoleModifiers mod = 0; + ConsoleKeyInfo newConsoleKeyInfo = default; - try - { - if (_inputQueue.Count == 0 || _forceRead) + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { - ConsoleKey key = 0; - ConsoleModifiers mod = 0; - ConsoleKeyInfo newConsoleKeyInfo = default; + ConsoleKeyInfo consoleKeyInfo; - while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + try { - ConsoleKeyInfo consoleKeyInfo; + consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } - try + var ckiAlreadyResized = false; + + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) + { + ckiAlreadyResized = true; + + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); + AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; + + if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B') { - consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - var ckiAlreadyResized = false; - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) - { - ckiAlreadyResized = true; - - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); - AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; - - if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B') - { - _isEscSeq = true; - } - } - - if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) - || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) - { - if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) - { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( - new ( - (char)KeyCode.Esc, - 0, - false, - false, - false - ), - _cki - ); - } - _isEscSeq = true; + } + } - if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) - || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) - || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar))) - { - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - } - else - { - newConsoleKeyInfo = consoleKeyInfo; - - if (!ckiAlreadyResized) - { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - } - - if (Console.KeyAvailable) - { - continue; - } - - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - } - - break; + if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) + || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) + { + if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( + new ( + (char)KeyCode.Esc, + 0, + false, + false, + false + ), + _cki + ); } - if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) + _isEscSeq = true; + + if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) + || (_cki is { } + && char.IsLetter (_cki [^1].KeyChar) + && char.IsLower (consoleKeyInfo.KeyChar) + && char.IsLetter (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar))) { ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; + _isEscSeq = false; + ProcessResponse (); - if (Console.KeyAvailable) + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + else + { + newConsoleKeyInfo = consoleKeyInfo; + + if (!ckiAlreadyResized) { _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); } - else + + if (Console.KeyAvailable) { - ProcessMapConsoleKeyInfo (consoleKeyInfo); + continue; } - break; - } - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - - if (_retries > 0) - { - _retries = 0; + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + ProcessResponse (); } break; } + + if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) + { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + ProcessResponse (); + + if (Console.KeyAvailable) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + } + else + { + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + + break; + } + + ProcessMapConsoleKeyInfo (consoleKeyInfo); + + if (_retries > 0) + { + _retries = 0; + } + + break; } - _inputReady.Set (); + if (!_inputReady.IsSet) + { + _inputReady.Set (); + } } catch (OperationCanceledException) { @@ -394,12 +383,12 @@ internal class NetEvents : IDisposable return; } - //if (seqReqStatus is { }) - //{ - // HandleRequestResponseEvent (c1Control, code, values, terminating); + if (seqReqStatus is { }) + { + //HandleRequestResponseEvent (c1Control, code, values, terminating); - // return; - //} + return; + } if (newConsoleKeyInfo != default) { @@ -731,7 +720,6 @@ internal class NetEvents : IDisposable _inputReadyCancellationTokenSource = null; _inputReady.Dispose (); - _waitForStart.Dispose (); try { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 36490b32f..03c940106 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -20,6 +20,8 @@ internal class NetMainLoop : IMainLoopDriver private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly Queue _resultQueue = new (); private MainLoop? _mainLoop; + bool IMainLoopDriver._forceRead { get; set; } + ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); /// Initializes the class with the console driver. /// Passing a consoleDriver is provided to capture windows resizing. @@ -116,12 +118,12 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !_netEvents!._forceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) { _waitForProbe.Wait (_inputHandlerTokenSource.Token); } - if (_resultQueue?.Count == 0 || _netEvents!._forceRead) + if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) { NetEvents.InputResult? result = _netEvents!.DequeueInput (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 34afbc056..5eaefd618 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -187,68 +187,6 @@ internal class WindowsDriver : ConsoleDriver } } - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (_mainLoopDriver is null) - { - return false; - } - - _ansiResponseTokenSource ??= new (); - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - _mainLoopDriver._forceRead = true; - } - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - catch (OperationCanceledException) - { - return false; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - internal override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } #region Not Implemented diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index ca10a693f..ce23aa864 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -21,7 +21,7 @@ internal class WindowsMainLoop : IMainLoopDriver // The records that we keep fetching private readonly Queue _resultQueue = new (); - private readonly ManualResetEventSlim _waitForProbe = new (false); + ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -57,7 +57,7 @@ internal class WindowsMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForProbe.Set (); + ((IMainLoopDriver)this)._waitForInput.Set (); #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif @@ -131,7 +131,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - _waitForProbe?.Dispose (); + ((IMainLoopDriver)this)._waitForInput?.Dispose (); _resultQueue.Clear (); @@ -146,7 +146,7 @@ internal class WindowsMainLoop : IMainLoopDriver _mainLoop = null; } - internal bool _forceRead; + public bool _forceRead { get; set; } private void WindowsInputHandler () { @@ -156,7 +156,7 @@ internal class WindowsMainLoop : IMainLoopDriver { if (_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } if (_resultQueue?.Count == 0 || _forceRead) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index f281dc461..8c140d0ee 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -293,7 +293,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario }; bool success = Application.Driver!.TryWriteAnsiRequest ( - ansiEscapeSequenceRequest + Application.MainLoop.MainLoopDriver, + ref ansiEscapeSequenceRequest ); tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? ""; @@ -369,7 +370,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario { _sends.Add (DateTime.Now); AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; - if (Application.Driver!.TryWriteAnsiRequest (ansiRequest)) + if (Application.Driver!.TryWriteAnsiRequest (Application.MainLoop.MainLoopDriver, ref ansiRequest)) { HandleResponse (ansiRequest.AnsiEscapeSequenceResponse?.Response); } diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 47812926d..3b77dd775 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -946,10 +946,14 @@ public class MainLoopTests private class TestMainloop : IMainLoopDriver { private MainLoop mainLoop; + private bool _forceRead1; + private ManualResetEventSlim _waitForInput1; public bool EventsPending () { throw new NotImplementedException (); } public void Iteration () { throw new NotImplementedException (); } public void TearDown () { throw new NotImplementedException (); } public void Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; } public void Wakeup () { throw new NotImplementedException (); } + public bool _forceRead { get; set; } + public ManualResetEventSlim _waitForInput { get; set; } } } From 7d146b74206d4c6963149857fb180e99680062e7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 01:12:38 +0000 Subject: [PATCH 106/151] Fix unit test which was causing With_Subview_Using_PosFunc failing. --- UnitTests/View/Adornment/PaddingTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UnitTests/View/Adornment/PaddingTests.cs b/UnitTests/View/Adornment/PaddingTests.cs index 0242bcc1d..a4defc997 100644 --- a/UnitTests/View/Adornment/PaddingTests.cs +++ b/UnitTests/View/Adornment/PaddingTests.cs @@ -33,5 +33,7 @@ PPP", output ); TestHelpers.AssertDriverAttributesAre ("0", output, null, view.GetNormalColor ()); + + ((FakeDriver)Application.Driver!).End (); } } From 3425c5a30d7c4ce24173d55b9767cc024274d699 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 15:33:11 +0000 Subject: [PATCH 107/151] Forgot to use _waitForInput.Reset and remove unnecessary finally block. --- .../WindowsDriver/WindowsMainLoop.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index ce23aa864..1eec1f03f 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -156,7 +156,16 @@ internal class WindowsMainLoop : IMainLoopDriver { if (_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) { - ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); + try + { + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + ((IMainLoopDriver)this)._waitForInput.Reset (); } if (_resultQueue?.Count == 0 || _forceRead) @@ -168,19 +177,13 @@ internal class WindowsMainLoop : IMainLoopDriver _resultQueue!.Enqueue (result.Value); } } - } catch (OperationCanceledException) { return; } - finally - { - if (_inputHandlerTokenSource is { IsCancellationRequested: false }) - { - _eventReady.Set (); - } - } + + _eventReady.Set (); } } From 644afa96d3a47702597cfabd66f3776b4b0a150d Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 16:02:45 +0000 Subject: [PATCH 108/151] Change filter in the ansi request scenario. --- .../Scenarios/AnsiEscapeSequenceRequests.cs | 49 +++++++++++-------- .../Input/AnsiEscapeSequenceRequestsTests.cs | 7 +++ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index 8c140d0ee..f9ce6c4cc 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -9,12 +9,11 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Tests")] public sealed class AnsiEscapeSequenceRequests : Scenario { - private readonly List _sends = new (); + private readonly Dictionary _sends = new (); private readonly object _lockAnswers = new (); - private readonly Dictionary _answers = new (); - private readonly object _lockErrors = new (); - private readonly Dictionary _errors = new (); + private readonly Dictionary _answers = new (); + private readonly Dictionary _errors = new (); private GraphView _graphView; @@ -117,7 +116,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario cbDar.ValueChanging += (s, e) => { - if (e.NewValue < 0 || e.NewValue > 20) + if (e.NewValue is < 0 or > 20) { e.Cancel = true; } @@ -327,7 +326,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return "No requests sent yet"; } - string last = _answers.Last ().Value; + string last = _answers.Last ().Value.AnsiEscapeSequenceResponse!.Response; int unique = _answers.Values.Distinct ().Count (); int total = _answers.Count; @@ -342,7 +341,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return "No errors received yet"; } - string last = _errors.Last ().Value; + string last = _errors.Last ().Value.AnsiEscapeSequenceResponse!.Error; int unique = _errors.Values.Distinct ().Count (); int total = _errors.Count; @@ -350,33 +349,34 @@ public sealed class AnsiEscapeSequenceRequests : Scenario return $"Last:{last} U:{unique} T:{total}"; } - private void HandleResponse (string response) + private void HandleResponse (AnsiEscapeSequenceRequest ansiRequest) { lock (_lockAnswers) { - _answers.Add (DateTime.Now, response); + _answers.Add (DateTime.Now, ansiRequest); } } - private void HandleResponseError (string response) + private void HandleResponseError (AnsiEscapeSequenceRequest ansiRequest) { lock (_lockAnswers) { - _errors.Add (DateTime.Now, response); + _errors.Add (DateTime.Now, ansiRequest); } } private void SendDar () { - _sends.Add (DateTime.Now); AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; - if (Application.Driver!.TryWriteAnsiRequest (Application.MainLoop.MainLoopDriver, ref ansiRequest)) + _sends.Add (DateTime.Now, ansiRequest); + + if (Application.Driver!.TryWriteAnsiRequest (Application.MainLoop!.MainLoopDriver, ref ansiRequest)) { - HandleResponse (ansiRequest.AnsiEscapeSequenceResponse?.Response); + HandleResponse (ansiRequest); } else { - HandleResponseError (ansiRequest.AnsiEscapeSequenceResponse?.Response); + HandleResponseError (ansiRequest); } } @@ -398,19 +398,26 @@ public sealed class AnsiEscapeSequenceRequests : Scenario _graphView.GraphColor = new Attribute (Color.Green, Color.Black); } - private int ToSeconds (DateTime t) { return (int)(DateTime.Now - t).TotalSeconds; } + private static Func, int> ToSeconds () { return t => (int)(DateTime.Now - t.Key).TotalSeconds; } private void UpdateGraph () { _sentSeries.Points = _sends - .GroupBy (ToSeconds) + .Where ( + r => r.Value?.AnsiEscapeSequenceResponse is null + || (r.Value?.AnsiEscapeSequenceResponse is { } + && string.IsNullOrEmpty (r.Value?.AnsiEscapeSequenceResponse.Response))) + .GroupBy (ToSeconds ()) .Select (g => new PointF (g.Key, g.Count ())) .ToList (); - _answeredSeries.Points = _answers.Keys - .GroupBy (ToSeconds) - .Select (g => new PointF (g.Key, g.Count ())) - .ToList (); + _answeredSeries.Points = _answers + .Where ( + r => r.Value.AnsiEscapeSequenceResponse is { } + && !string.IsNullOrEmpty (r.Value?.AnsiEscapeSequenceResponse.Response)) + .GroupBy (ToSeconds ()) + .Select (g => new PointF (g.Key, g.Count ())) + .ToList (); // _graphView.ScrollOffset = new PointF(,0); if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) diff --git a/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs index aadcc77b7..1658fb469 100644 --- a/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs +++ b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs @@ -69,4 +69,11 @@ public class AnsiEscapeSequenceRequestsTests Assert.NotNull (seqReqStatus); Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator); } + + [Fact] + public void Request_Initialization_AnsiEscapeSequenceResponse_Is_Null () + { + AnsiEscapeSequenceRequest ansiRequest = new () { Request = "\u001b[0c", Terminator = "c"}; + Assert.Null (ansiRequest.AnsiEscapeSequenceResponse); + } } From 1b830bded75a448ad0056cf7b0b3469ed524d939 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 16:44:39 +0000 Subject: [PATCH 109/151] Cleanup code. --- .../CursesDriver/UnixMainLoop.cs | 235 +++++++++--------- 1 file changed, 120 insertions(+), 115 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 84fa67ad3..259f84872 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -106,7 +106,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e) { - _pollDataQueue!.Enqueue (EnqueueMouseEvent (e.Flags, e.Position)); + _pollDataQueue.Enqueue (EnqueueMouseEvent (e.Flags, e.Position)); } private void WindowSizeHandler () @@ -137,7 +137,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver rows = ws.ws_row; cols = ws.ws_col; - _pollDataQueue!.Enqueue (EnqueueWindowSizeEvent (rows, cols)); + _pollDataQueue.Enqueue (EnqueueWindowSizeEvent (rows, cols)); break; } @@ -162,117 +162,125 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver try { if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) - { - ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return; - } - finally - { - if (!_inputHandlerTokenSource.IsCancellationRequested) - { - ((IMainLoopDriver)this)._waitForInput.Reset (); - } - } - - if (_pollDataQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) - { - while (!_inputHandlerTokenSource.IsCancellationRequested) { try { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } catch (OperationCanceledException) { return; } - int n = poll (_pollMap, (uint)_pollMap.Length, 0); + ((IMainLoopDriver)this)._waitForInput.Reset (); - if (n > 0) - { - // Check if stdin has data - if ((_pollMap [0].revents & (int)Condition.PollIn) != 0) - { - // Allocate memory for the buffer - var buf = new byte [2048]; - nint bufPtr = Marshal.AllocHGlobal (buf.Length); - try - { - // Read from the stdin - int bytesRead = read (_pollMap [0].fd, bufPtr, buf.Length); - - if (bytesRead > 0) - { - // Copy the data from unmanaged memory to a byte array - var buffer = new byte [bytesRead]; - Marshal.Copy (bufPtr, buffer, 0, bytesRead); - - // Convert the byte array to a string (assuming UTF-8 encoding) - string data = Encoding.UTF8.GetString (buffer); - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) - { - data = data.Insert (0, AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos)); - AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; - } - - // Enqueue the data - ProcessEnqueuePollData (data); - } - } - finally - { - // Free the allocated memory - Marshal.FreeHGlobal (bufPtr); - } - } - - if (_retries > 0) - { - _retries = 0; - } - - break; - } - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - if (_retries > 1) - { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus)) - { - lock (seqReqStatus!.AnsiRequest._responseLock) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } + ProcessInputQueue (); } } + catch (OperationCanceledException) + { + return; + } _eventReady.Set (); } } + private void ProcessInputQueue () + { + if (_pollDataQueue.Count == 0 || ((IMainLoopDriver)this)._forceRead) + { + while (!_inputHandlerTokenSource.IsCancellationRequested) + { + try + { + Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + int n = poll (_pollMap!, (uint)_pollMap!.Length, 0); + + if (n > 0) + { + // Check if stdin has data + if ((_pollMap [0].revents & (int)Condition.PollIn) != 0) + { + // Allocate memory for the buffer + var buf = new byte [2048]; + nint bufPtr = Marshal.AllocHGlobal (buf.Length); + + try + { + // Read from the stdin + int bytesRead = read (_pollMap [0].fd, bufPtr, buf.Length); + + if (bytesRead > 0) + { + // Copy the data from unmanaged memory to a byte array + var buffer = new byte [bytesRead]; + Marshal.Copy (bufPtr, buffer, 0, bytesRead); + + // Convert the byte array to a string (assuming UTF-8 encoding) + string data = Encoding.UTF8.GetString (buffer); + + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) + { + data = data.Insert (0, AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos)); + AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; + } + + // Enqueue the data + ProcessEnqueuePollData (data); + } + } + finally + { + // Free the allocated memory + Marshal.FreeHGlobal (bufPtr); + } + } + + if (_retries > 0) + { + _retries = 0; + } + + break; + } + + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) + { + if (_retries > 1) + { + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus)) + { + lock (seqReqStatus.AnsiRequest._responseLock) + { + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + } + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + } + } + } + private void ProcessEnqueuePollData (string pollData) { foreach (string split in AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (pollData)) @@ -309,7 +317,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { foreach (MouseFlags mf in mouseFlags) { - _pollDataQueue!.Enqueue (EnqueueMouseEvent (mf, pos)); + _pollDataQueue.Enqueue (EnqueueMouseEvent (mf, pos)); } return; @@ -317,7 +325,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver if (newConsoleKeyInfo != default) { - _pollDataQueue!.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo)); + _pollDataQueue.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo)); } } @@ -343,7 +351,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver ((IMainLoopDriver)this)._waitForInput.Set (); _windowSizeChange.Set (); - if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; } @@ -377,10 +385,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver // Dequeue and process the data while (_pollDataQueue.TryDequeue (out PollData inputRecords)) { - if (inputRecords is { }) - { - _cursesDriver.ProcessInput (inputRecords); - } + _cursesDriver.ProcessInput (inputRecords); } } @@ -388,17 +393,17 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; - _inputHandlerTokenSource?.Cancel (); - _inputHandlerTokenSource?.Dispose (); + _inputHandlerTokenSource.Cancel (); + _inputHandlerTokenSource.Dispose (); ((IMainLoopDriver)this)._waitForInput?.Dispose (); _windowSizeChange.Dispose(); - _pollDataQueue?.Clear (); + _pollDataQueue.Clear (); - _eventReadyTokenSource?.Cancel (); - _eventReadyTokenSource?.Dispose (); - _eventReady?.Dispose (); + _eventReadyTokenSource.Cancel (); + _eventReadyTokenSource.Dispose (); + _eventReady.Dispose (); _mainLoop = null; } @@ -474,13 +479,13 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver public readonly override string ToString () { - return EventType switch - { - EventType.Key => ToString (KeyEvent), - EventType.Mouse => MouseEvent.ToString (), - EventType.WindowSize => WindowSizeEvent.ToString (), - _ => "Unknown event type: " + EventType - }; + return (EventType switch + { + EventType.Key => ToString (KeyEvent), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowSize => WindowSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + })!; } /// Prints a ConsoleKeyInfoEx structure From 1795547702e893e3fb9ac27b24951482f64af9fc Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 16:59:15 +0000 Subject: [PATCH 110/151] ProcessInputQueue must be outside the wait block. --- Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 259f84872..60224bda3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -173,10 +173,9 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver } ((IMainLoopDriver)this)._waitForInput.Reset (); - - - ProcessInputQueue (); } + + ProcessInputQueue (); } catch (OperationCanceledException) { From 70e91f2b6ff97a5a7ebd97364279eb8c35151a78 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 17:37:12 +0000 Subject: [PATCH 111/151] Tidy up code. --- .../CursesDriver/UnixMainLoop.cs | 7 +++- .../ConsoleDrivers/NetDriver/NetEvents.cs | 2 +- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 40 ++++++++++--------- .../WindowsDriver/WindowsMainLoop.cs | 26 +++++++----- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 60224bda3..78ac697cb 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -181,8 +181,6 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { return; } - - _eventReady.Set (); } } @@ -278,6 +276,11 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver } } } + + if (_pollDataQueue.Count > 0) + { + _eventReady.Set (); + } } private void ProcessEnqueuePollData (string pollData) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index eb4ecf0d3..6aaae08a4 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -245,7 +245,7 @@ internal class NetEvents : IDisposable break; } - if (!_inputReady.IsSet) + if (_inputQueue.Count > 0) { _inputReady.Set (); } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 03c940106..2479fa1e0 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -120,34 +120,38 @@ internal class NetMainLoop : IMainLoopDriver { if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); - } - - if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) - { - NetEvents.InputResult? result = _netEvents!.DequeueInput (); - - if (result.HasValue) + try { - _resultQueue?.Enqueue (result.Value); + _waitForProbe.Wait (_inputHandlerTokenSource.Token); } + catch (OperationCanceledException) + { + return; + } + + _waitForProbe.Reset (); } - if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0) - { - _eventReady.Set (); - } + ProcessInputQueue (); } catch (OperationCanceledException) { return; } - finally + } + } + + private void ProcessInputQueue () + { + if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) + { + NetEvents.InputResult? result = _netEvents!.DequeueInput (); + + if (result.HasValue) { - if (_inputHandlerTokenSource is { IsCancellationRequested: false }) - { - _waitForProbe.Reset (); - } + _resultQueue?.Enqueue (result.Value); + + _eventReady.Set (); } } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 1eec1f03f..a4a14bf97 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -168,22 +168,28 @@ internal class WindowsMainLoop : IMainLoopDriver ((IMainLoopDriver)this)._waitForInput.Reset (); } - if (_resultQueue?.Count == 0 || _forceRead) - { - WindowsConsole.InputRecord? result = _winConsole!.DequeueInput (); - - if (result.HasValue) - { - _resultQueue!.Enqueue (result.Value); - } - } + ProcessInputQueue (); } catch (OperationCanceledException) { return; } - _eventReady.Set (); + } + } + + private void ProcessInputQueue () + { + if (_resultQueue?.Count == 0 || _forceRead) + { + WindowsConsole.InputRecord? result = _winConsole!.DequeueInput (); + + if (result.HasValue) + { + _resultQueue!.Enqueue (result.Value); + + _eventReady.Set (); + } } } From 967cbc205a9522d6d8cc4893245b9c033daa941a Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 18:25:49 +0000 Subject: [PATCH 112/151] Switch to ConcurrentQueue for all drivers. --- Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs | 8 +++++--- .../ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 2479fa1e0..f4fd26947 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Collections.Concurrent; + namespace Terminal.Gui; /// @@ -18,7 +20,7 @@ internal class NetMainLoop : IMainLoopDriver internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); - private readonly Queue _resultQueue = new (); + private readonly ConcurrentQueue _resultQueue = new (); private MainLoop? _mainLoop; bool IMainLoopDriver._forceRead { get; set; } ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); @@ -89,9 +91,9 @@ internal class NetMainLoop : IMainLoopDriver void IMainLoopDriver.Iteration () { - while (_resultQueue.Count > 0) + while (_resultQueue.TryDequeue (out NetEvents.InputResult inputRecords)) { - ProcessInput?.Invoke (_resultQueue.Dequeue ()); + ProcessInput?.Invoke (inputRecords); } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index a4a14bf97..d273ef1f9 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Collections.Concurrent; + namespace Terminal.Gui; /// @@ -20,7 +22,7 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); // The records that we keep fetching - private readonly Queue _resultQueue = new (); + private readonly ConcurrentQueue _resultQueue = new (); ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); @@ -102,9 +104,9 @@ internal class WindowsMainLoop : IMainLoopDriver void IMainLoopDriver.Iteration () { - while (_resultQueue.Count > 0) + while (_resultQueue.TryDequeue (out WindowsConsole.InputRecord inputRecords)) { - ((WindowsDriver)_consoleDriver).ProcessInput (_resultQueue.Dequeue ()); + ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords); } #if HACK_CHECK_WINCHANGED if (_winChanged) From ae1bdd16ff9c56443211c203ad751ecf92bdde4d Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 21:05:04 +0000 Subject: [PATCH 113/151] Fix a bug where for e.g. two 'c' terminator were included in the response. --- Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 50480b153..6c5a95b97 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -117,6 +117,7 @@ internal class WindowsConsole lock (seqReqStatus!.AnsiRequest._responseLock) { + readingSequence=false; raisedResponse = true; seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); // Clear the terminator for not be enqueued From 6bac65244c7a47a0b683ce0b9a4ae01e2de0b412 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 22:20:24 +0000 Subject: [PATCH 114/151] Fix ansiSequence bug not being clear after finish get the response which were adding a Esc character if pressed alone. --- Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 6c5a95b97..ae9223234 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -117,11 +117,13 @@ internal class WindowsConsole lock (seqReqStatus!.AnsiRequest._responseLock) { - readingSequence=false; + readingSequence = false; raisedResponse = true; seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); // Clear the terminator for not be enqueued inputRecord = default (InputRecord); + // Clear the ansiSequence to avoid insert another Esc character + ansiSequence.Clear (); } } From d31f43d47d15c86bb0d23089e84c5e2f23d9cc56 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 23:09:59 +0000 Subject: [PATCH 115/151] Add ObjectDisposedException to the catch block. --- Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 9 +++++++-- Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs | 9 +++++++-- .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 9 +++++++-- .../ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs | 9 +++++++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 78ac697cb..a9d531569 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -167,9 +167,14 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } - catch (OperationCanceledException) + catch (Exception ex) { - return; + if (ex is OperationCanceledException or ObjectDisposedException) + { + return; + } + + throw; } ((IMainLoopDriver)this)._waitForInput.Reset (); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index f4fd26947..3957360bb 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -126,9 +126,14 @@ internal class NetMainLoop : IMainLoopDriver { _waitForProbe.Wait (_inputHandlerTokenSource.Token); } - catch (OperationCanceledException) + catch (Exception ex) { - return; + if (ex is OperationCanceledException or ObjectDisposedException) + { + return; + } + + throw; } _waitForProbe.Reset (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index ae9223234..2db0b4077 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -191,9 +191,14 @@ internal class WindowsConsole } } } - catch (Exception) + catch (Exception ex) { - return null; + if (ex is OperationCanceledException or ObjectDisposedException) + { + return null; + } + + throw; } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index d273ef1f9..103afc3d5 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -162,9 +162,14 @@ internal class WindowsMainLoop : IMainLoopDriver { ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } - catch (OperationCanceledException) + catch (Exception ex) { - return; + if (ex is OperationCanceledException or ObjectDisposedException) + { + return; + } + + throw; } ((IMainLoopDriver)this)._waitForInput.Reset (); From 7cd844038bba63e12d2194181f836475fdeb28fc Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 23:11:33 +0000 Subject: [PATCH 116/151] Switch to IMainLoopDriver._waitForInput. --- Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 3957360bb..e6a29c58b 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -17,7 +17,6 @@ internal class NetMainLoop : IMainLoopDriver internal Action? ProcessInput; private readonly ManualResetEventSlim _eventReady = new (false); - internal readonly ManualResetEventSlim _waitForProbe = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly ConcurrentQueue _resultQueue = new (); @@ -53,7 +52,7 @@ internal class NetMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForProbe.Set (); + ((IMainLoopDriver)this)._waitForInput.Set (); if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { @@ -105,7 +104,7 @@ internal class NetMainLoop : IMainLoopDriver _eventReadyTokenSource.Dispose (); _eventReady.Dispose (); - _waitForProbe.Dispose (); + ((IMainLoopDriver)this)._waitForInput.Dispose (); _resultQueue.Clear (); _netEvents?.Dispose (); @@ -124,7 +123,7 @@ internal class NetMainLoop : IMainLoopDriver { try { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } catch (Exception ex) { @@ -136,7 +135,7 @@ internal class NetMainLoop : IMainLoopDriver throw; } - _waitForProbe.Reset (); + ((IMainLoopDriver)this)._waitForInput.Reset (); } ProcessInputQueue (); From 54f62645a238440b2a552a62a916479a89e99fe1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 23:18:56 +0000 Subject: [PATCH 117/151] Add ProcessAnsiRequestHandler method task. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 163 ++++++++++++------ .../CursesDriver/CursesDriver.cs | 2 + .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 3 - .../ConsoleDrivers/NetDriver/NetDriver.cs | 5 + .../WindowsDriver/WindowsDriver.cs | 2 + 5 files changed, 115 insertions(+), 60 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 6caf6948f..3a54168fe 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Collections.Concurrent; using System.Diagnostics; namespace Terminal.Gui; @@ -12,6 +13,79 @@ namespace Terminal.Gui; /// public abstract class ConsoleDriver { + private readonly ManualResetEventSlim _waitAnsiRequest = new (false); + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource? _ansiRequestTokenSource = new (); + private readonly ConcurrentQueue _requestQueue = new (); + private readonly ConcurrentQueue _responseQueue = new (); + private IMainLoopDriver? _mainLoopDriver; + + internal void ProcessAnsiRequestHandler () + { + while (_ansiRequestTokenSource is { IsCancellationRequested: false}) + { + try + { + if (_requestQueue.Count == 0) + { + try + { + _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + _waitAnsiRequest.Reset (); + } + + while (_requestQueue.TryDequeue (out AnsiEscapeSequenceRequest? ansiRequest)) + { + try + { + lock (ansiRequest._responseLock) + { + AnsiEscapeSequenceRequest? request = ansiRequest; + + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == request); + Debug.Assert (e == request.AnsiEscapeSequenceResponse); + + _responseQueue.Enqueue (request); + + _waitAnsiResponse.Set (); + }; + + AnsiEscapeSequenceRequests.Add (ansiRequest); + + WriteRaw (ansiRequest.Request); + + _mainLoopDriver!._forceRead = true; + } + + if (!_ansiRequestTokenSource.IsCancellationRequested) + { + _mainLoopDriver._waitForInput.Set (); + + _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return; + } + + } + } + catch (OperationCanceledException) + { + return; + } + } + } + /// /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. /// @@ -32,82 +106,57 @@ public abstract class ConsoleDriver #region ANSI Esc Sequence Handling - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - // QUESTION: Should this be virtual with a default implementation that does the common stuff? - // QUESTION: Looking at the implementations of this method, there is TONs of duplicated code. - // QUESTION: We should figure out how to find just the things that are unique to each driver and - // QUESTION: create more fine-grained APIs to handle those. /// - /// Provide handling for the terminal write ANSI escape sequence request. + /// Provide unique handling for all the terminal write ANSI escape sequence request. /// /// The object. /// The object. - /// The request response. - public virtual bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) + /// if the request response is valid, otherwise. + public bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) { - if (mainLoopDriver is null) - { - return false; - } + ArgumentNullException.ThrowIfNull (mainLoopDriver, nameof (mainLoopDriver)); + ArgumentNullException.ThrowIfNull (ansiRequest, nameof (ansiRequest)); - _ansiResponseTokenSource ??= new (); + lock (ansiRequest._responseLock) + { + _mainLoopDriver = mainLoopDriver; + _requestQueue.Enqueue (ansiRequest); + + _waitAnsiRequest.Set (); + } try { + _waitAnsiResponse.Wait (_ansiRequestTokenSource!.Token); + + _waitAnsiResponse.Reset (); + + _responseQueue.TryDequeue (out _); + lock (ansiRequest._responseLock) { - AnsiEscapeSequenceRequest? request = ansiRequest; + _mainLoopDriver._forceRead = false; - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == request); - Debug.Assert (e == request.AnsiEscapeSequenceResponse); + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + { + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (request.AnsiRequest._responseLock) + { + // Bad request or no response at all + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + } + } + } - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - mainLoopDriver._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - mainLoopDriver._waitForInput.Set (); - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; } } catch (OperationCanceledException) { return false; } - - lock (ansiRequest._responseLock) - { - mainLoopDriver._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } } // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 84c74fa20..c56b7b352 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -679,6 +679,8 @@ internal class CursesDriver : ConsoleDriver { Curses.refresh (); } + + Task.Run (ProcessAnsiRequestHandler); } return new (_mainLoopDriver); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 64a3415da..7d82737cc 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -392,9 +392,6 @@ public class FakeDriver : ConsoleDriver MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control)); } - /// - public override bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } - /// internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index f66d921bf..91746a218 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -294,6 +294,11 @@ internal class NetDriver : ConsoleDriver _mainLoopDriver = new (this); _mainLoopDriver.ProcessInput = ProcessInput; + if (!RunningUnitTests) + { + Task.Run (ProcessAnsiRequestHandler); + } + return new (_mainLoopDriver); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 5eaefd618..51df2c4d8 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -481,6 +481,8 @@ internal class WindowsDriver : ConsoleDriver if (!RunningUnitTests) { WinConsole?.SetInitialCursorVisibility (); + + Task.Run (ProcessAnsiRequestHandler); } return new MainLoop (_mainLoopDriver); From a3c961ef3f3fa78bc9988a076731b5c8e192a51e Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 23:27:47 +0000 Subject: [PATCH 118/151] Prevents an alone escape being eating by the response. --- .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 2db0b4077..b27b8fe96 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -91,14 +91,15 @@ internal class WindowsConsole // Check if input is part of an ANSI escape sequence if (inputChar == '\u001B') // Escape character { - // Peek to check if there is any input available with key event and bKeyDown + // Peek to check if there is any input available with key event and bKeyDown and not Escape character if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0) { - if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true }) + if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true, KeyEvent.UnicodeChar: not '\u001B' }) { // It's really an ANSI request response readingSequence = true; - ansiSequence.Clear (); // Start a new sequence + // Start a new sequence ensuring in the cases where wasn't clear by reading the terminator + ansiSequence.Clear (); ansiSequence.Append (inputChar); continue; @@ -127,7 +128,10 @@ internal class WindowsConsole } } - continue; + if (readingSequence) + { + continue; + } } } } From 5b76f4d2d09788abb9a8cdd0985e4ebc20ce39bd Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 12 Nov 2024 00:33:10 +0000 Subject: [PATCH 119/151] Remove the answer from the _sends variable. Normally it's always zero. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index f9ce6c4cc..c14fb4afd 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -292,7 +292,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario }; bool success = Application.Driver!.TryWriteAnsiRequest ( - Application.MainLoop.MainLoopDriver, + Application.MainLoop!.MainLoopDriver, ref ansiEscapeSequenceRequest ); @@ -354,6 +354,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario lock (_lockAnswers) { _answers.Add (DateTime.Now, ansiRequest); + KeyValuePair found = _sends.First (r => r.Value == ansiRequest); + _sends.Remove (found.Key); } } @@ -362,6 +364,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario lock (_lockAnswers) { _errors.Add (DateTime.Now, ansiRequest); + KeyValuePair found = _sends.First (r => r.Value == ansiRequest); + _sends.Remove (found.Key); } } @@ -402,11 +406,9 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void UpdateGraph () { + System.Diagnostics.Debug.Assert (_sends.Count == 0); + _sentSeries.Points = _sends - .Where ( - r => r.Value?.AnsiEscapeSequenceResponse is null - || (r.Value?.AnsiEscapeSequenceResponse is { } - && string.IsNullOrEmpty (r.Value?.AnsiEscapeSequenceResponse.Response))) .GroupBy (ToSeconds ()) .Select (g => new PointF (g.Key, g.Count ())) .ToList (); From 53ea0a83f38dd36b17373e94bc329f4cf3311f56 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 13 Nov 2024 17:51:52 +0000 Subject: [PATCH 120/151] Replace to ForceRead and to WaitForInput. --- Terminal.Gui/Application/MainLoop.cs | 17 ++++++++++------- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 6 +++--- .../ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 16 ++++++++-------- .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 4 ++-- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 16 ++++++++-------- .../WindowsDriver/WindowsMainLoop.cs | 16 ++++++++-------- UnitTests/Application/MainLoopTests.cs | 4 ++-- 7 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs index d3cce5efe..c293ac70d 100644 --- a/Terminal.Gui/Application/MainLoop.cs +++ b/Terminal.Gui/Application/MainLoop.cs @@ -30,9 +30,15 @@ public interface IMainLoopDriver /// Wakes up the that might be waiting on input, must be thread safe. void Wakeup (); - bool _forceRead { get; set; } + /// + /// Flag to force reading input instead of call . + /// + bool ForceRead { get; set; } - ManualResetEventSlim _waitForInput { get; set; } + /// + /// Switch for waiting for input or signaled to resume. + /// + ManualResetEventSlim WaitForInput { get; set; } } /// The MainLoop monitors timers and idle handlers. @@ -134,10 +140,7 @@ public class MainLoop : IDisposable /// internal object AddTimeout (TimeSpan time, Func callback) { - if (callback is null) - { - throw new ArgumentNullException (nameof (callback)); - } + ArgumentNullException.ThrowIfNull (callback); var timeout = new Timeout { Span = time, Callback = callback }; AddTimeout (time, timeout); @@ -276,7 +279,7 @@ public class MainLoop : IDisposable MainLoopDriver.Iteration (); - var runIdle = false; + bool runIdle; lock (_idleHandlersLock) { diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 3a54168fe..ec2b76849 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -62,12 +62,12 @@ public abstract class ConsoleDriver WriteRaw (ansiRequest.Request); - _mainLoopDriver!._forceRead = true; + _mainLoopDriver!.ForceRead = true; } if (!_ansiRequestTokenSource.IsCancellationRequested) { - _mainLoopDriver._waitForInput.Set (); + _mainLoopDriver.WaitForInput.Set (); _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); } @@ -135,7 +135,7 @@ public abstract class ConsoleDriver lock (ansiRequest._responseLock) { - _mainLoopDriver._forceRead = false; + _mainLoopDriver.ForceRead = false; if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) { diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index a9d531569..96e7a89f7 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -43,7 +43,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver private Pollfd []? _pollMap; private readonly ConcurrentQueue _pollDataQueue = new (); private readonly ManualResetEventSlim _eventReady = new (false); - ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); + ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false); private readonly ManualResetEventSlim _windowSizeChange = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -152,7 +152,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver } } - bool IMainLoopDriver._forceRead { get; set; } + bool IMainLoopDriver.ForceRead { get; set; } private int _retries; private void CursesInputHandler () @@ -161,11 +161,11 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead) { try { - ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token); } catch (Exception ex) { @@ -177,7 +177,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver throw; } - ((IMainLoopDriver)this)._waitForInput.Reset (); + ((IMainLoopDriver)this).WaitForInput.Reset (); } ProcessInputQueue (); @@ -191,7 +191,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver private void ProcessInputQueue () { - if (_pollDataQueue.Count == 0 || ((IMainLoopDriver)this)._forceRead) + if (_pollDataQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead) { while (!_inputHandlerTokenSource.IsCancellationRequested) { @@ -355,7 +355,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - ((IMainLoopDriver)this)._waitForInput.Set (); + ((IMainLoopDriver)this).WaitForInput.Set (); _windowSizeChange.Set (); if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) @@ -402,7 +402,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver _inputHandlerTokenSource.Cancel (); _inputHandlerTokenSource.Dispose (); - ((IMainLoopDriver)this)._waitForInput?.Dispose (); + ((IMainLoopDriver)this).WaitForInput?.Dispose (); _windowSizeChange.Dispose(); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index 16a1a76b7..e6ea9cab0 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -3,8 +3,8 @@ internal class FakeMainLoop : IMainLoopDriver { public Action MockKeyPressed; - public bool _forceRead { get; set; } - public ManualResetEventSlim _waitForInput { get; set; } = new (); + public bool ForceRead { get; set; } + public ManualResetEventSlim WaitForInput { get; set; } = new (); public FakeMainLoop (ConsoleDriver consoleDriver = null) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index e6a29c58b..84f8437ce 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -21,8 +21,8 @@ internal class NetMainLoop : IMainLoopDriver private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly ConcurrentQueue _resultQueue = new (); private MainLoop? _mainLoop; - bool IMainLoopDriver._forceRead { get; set; } - ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); + bool IMainLoopDriver.ForceRead { get; set; } + ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false); /// Initializes the class with the console driver. /// Passing a consoleDriver is provided to capture windows resizing. @@ -52,7 +52,7 @@ internal class NetMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - ((IMainLoopDriver)this)._waitForInput.Set (); + ((IMainLoopDriver)this).WaitForInput.Set (); if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { @@ -104,7 +104,7 @@ internal class NetMainLoop : IMainLoopDriver _eventReadyTokenSource.Dispose (); _eventReady.Dispose (); - ((IMainLoopDriver)this)._waitForInput.Dispose (); + ((IMainLoopDriver)this).WaitForInput.Dispose (); _resultQueue.Clear (); _netEvents?.Dispose (); @@ -119,11 +119,11 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead) { try { - ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token); } catch (Exception ex) { @@ -135,7 +135,7 @@ internal class NetMainLoop : IMainLoopDriver throw; } - ((IMainLoopDriver)this)._waitForInput.Reset (); + ((IMainLoopDriver)this).WaitForInput.Reset (); } ProcessInputQueue (); @@ -149,7 +149,7 @@ internal class NetMainLoop : IMainLoopDriver private void ProcessInputQueue () { - if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) + if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this).ForceRead) { NetEvents.InputResult? result = _netEvents!.DequeueInput (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 103afc3d5..13c8421fd 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -23,7 +23,7 @@ internal class WindowsMainLoop : IMainLoopDriver // The records that we keep fetching private readonly ConcurrentQueue _resultQueue = new (); - ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); + ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -59,7 +59,7 @@ internal class WindowsMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - ((IMainLoopDriver)this)._waitForInput.Set (); + ((IMainLoopDriver)this).WaitForInput.Set (); #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif @@ -133,7 +133,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - ((IMainLoopDriver)this)._waitForInput?.Dispose (); + ((IMainLoopDriver)this).WaitForInput?.Dispose (); _resultQueue.Clear (); @@ -148,7 +148,7 @@ internal class WindowsMainLoop : IMainLoopDriver _mainLoop = null; } - public bool _forceRead { get; set; } + public bool ForceRead { get; set; } private void WindowsInputHandler () { @@ -156,11 +156,11 @@ internal class WindowsMainLoop : IMainLoopDriver { try { - if (_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) + if (_inputHandlerTokenSource.IsCancellationRequested && !ForceRead) { try { - ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token); } catch (Exception ex) { @@ -172,7 +172,7 @@ internal class WindowsMainLoop : IMainLoopDriver throw; } - ((IMainLoopDriver)this)._waitForInput.Reset (); + ((IMainLoopDriver)this).WaitForInput.Reset (); } ProcessInputQueue (); @@ -187,7 +187,7 @@ internal class WindowsMainLoop : IMainLoopDriver private void ProcessInputQueue () { - if (_resultQueue?.Count == 0 || _forceRead) + if (_resultQueue?.Count == 0 || ForceRead) { WindowsConsole.InputRecord? result = _winConsole!.DequeueInput (); diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 3b77dd775..db78ebe3b 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -953,7 +953,7 @@ public class MainLoopTests public void TearDown () { throw new NotImplementedException (); } public void Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; } public void Wakeup () { throw new NotImplementedException (); } - public bool _forceRead { get; set; } - public ManualResetEventSlim _waitForInput { get; set; } + public bool ForceRead { get; set; } + public ManualResetEventSlim WaitForInput { get; set; } } } From be1dc806afc5d9012da0cf77be7ae95461868642 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 13 Nov 2024 17:53:46 +0000 Subject: [PATCH 121/151] Fix comment. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index acb2c59a0..39a193b07 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -3,8 +3,7 @@ namespace Terminal.Gui; /// /// Describes an ongoing ANSI request sent to the console. -/// Use to handle the response -/// when the console answers the request. +/// Send a request using which will return the response. /// public class AnsiEscapeSequenceRequest { From f0b2474b6fc7ba60cd3438a6dc6b089c616a32d3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 13 Nov 2024 19:28:07 +0000 Subject: [PATCH 122/151] Code cleanup. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 8 ++++---- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 6 ++---- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 2 +- Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index ec2b76849..35ed85b8b 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -46,7 +46,7 @@ public abstract class ConsoleDriver { lock (ansiRequest._responseLock) { - AnsiEscapeSequenceRequest? request = ansiRequest; + AnsiEscapeSequenceRequest request = ansiRequest; ansiRequest.ResponseFromInput += (s, e) => { @@ -179,7 +179,7 @@ public abstract class ConsoleDriver /// Gets the location and size of the terminal screen. internal Rectangle Screen => new (0, 0, Cols, Rows); - private Region? _clip = null; + private Region? _clip; /// /// Gets or sets the clip rectangle that and are subject @@ -248,7 +248,7 @@ public abstract class ConsoleDriver /// if the coordinate is outside the screen bounds or outside of . /// otherwise. /// - public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); } + public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row); } /// /// Updates and to the specified column and row in . @@ -523,7 +523,7 @@ public abstract class ConsoleDriver } Contents [r, c] = new Cell { - Rune = (rune != default ? rune : (Rune)' '), + Rune = rune != default ? rune : (Rune)' ', Attribute = CurrentAttribute, IsDirty = true }; _dirtyLines! [r] = true; diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index c56b7b352..98cdbdba9 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -3,7 +3,6 @@ // Driver.cs: Curses-based Driver // -using System.Diagnostics; using System.Runtime.InteropServices; using Terminal.Gui.ConsoleDrivers; using Unix.Terminal; @@ -58,12 +57,11 @@ internal class CursesDriver : ConsoleDriver { // Not a valid location (outside screen or clip region) // Move within the clip region, then AddRune will actually move to Col, Row - Rectangle clipRect = Clip.GetBounds (); + Rectangle clipRect = Clip!.GetBounds (); Curses.move (clipRect.Y, clipRect.X); } } - public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) { KeyCode key; @@ -580,7 +578,7 @@ internal class CursesDriver : ConsoleDriver #region Init/End/MainLoop - public Curses.Window? _window; + private Curses.Window? _window; private UnixMainLoop? _mainLoopDriver; internal override MainLoop Init () diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 6aaae08a4..fc6120f8a 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -208,7 +208,7 @@ internal class NetEvents : IDisposable continue; } - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki!, ref mod); _cki = null; _isEscSeq = false; ProcessResponse (); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 84f8437ce..9ffc1298e 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -149,13 +149,13 @@ internal class NetMainLoop : IMainLoopDriver private void ProcessInputQueue () { - if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this).ForceRead) + if (_resultQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead) { NetEvents.InputResult? result = _netEvents!.DequeueInput (); if (result.HasValue) { - _resultQueue?.Enqueue (result.Value); + _resultQueue.Enqueue (result.Value); _eventReady.Set (); } From efba7846a9538f5134184f57283f7e0d3535a571 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 13 Nov 2024 19:29:11 +0000 Subject: [PATCH 123/151] Using _timeoutsLockToken instead of the _timeouts for all. --- Terminal.Gui/Application/MainLoop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs index c293ac70d..9ee974c7c 100644 --- a/Terminal.Gui/Application/MainLoop.cs +++ b/Terminal.Gui/Application/MainLoop.cs @@ -163,7 +163,7 @@ public class MainLoop : IDisposable waitTimeout = 0; - lock (_timeouts) + lock (_timeoutsLockToken) { if (_timeouts.Count > 0) { @@ -269,7 +269,7 @@ public class MainLoop : IDisposable /// internal void RunIteration () { - lock (_timeouts) + lock (_timeoutsLockToken) { if (_timeouts.Count > 0) { From 302a7cffb5dc88daab39548ba4c76c5e43b21899 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 13 Nov 2024 19:30:19 +0000 Subject: [PATCH 124/151] Decrease interval if there is more sent requests. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index c14fb4afd..a3d77d90b 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -125,16 +125,17 @@ public sealed class AnsiEscapeSequenceRequests : Scenario int lastSendTime = Environment.TickCount; var lockObj = new object (); + int interval = 50; Application.AddTimeout ( - TimeSpan.FromMilliseconds (50), + TimeSpan.FromMilliseconds ((double)interval / (cbDar.Value > 0 ? cbDar.Value : 1)), () => { lock (lockObj) { if (cbDar.Value > 0) { - int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds + interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds int currentTime = Environment.TickCount; // Current system time in milliseconds // Check if the time elapsed since the last send is greater than the interval From ce0183fea53e7123b6d018c7f211edae0d99f18e Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Nov 2024 12:36:10 +0000 Subject: [PATCH 125/151] Avoids exit after read the response with empty key. --- Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index b27b8fe96..afc3263f2 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -123,6 +123,8 @@ internal class WindowsConsole seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); // Clear the terminator for not be enqueued inputRecord = default (InputRecord); + // Clear numberEventsRead to not exit + numberEventsRead = 0; // Clear the ansiSequence to avoid insert another Esc character ansiSequence.Clear (); } From a649b12c4e57da91d0d1bf206d5f964005824fec Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Nov 2024 12:37:49 +0000 Subject: [PATCH 126/151] Rename to _ansiResponseReady. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 35ed85b8b..705f60e3a 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -14,7 +14,7 @@ namespace Terminal.Gui; public abstract class ConsoleDriver { private readonly ManualResetEventSlim _waitAnsiRequest = new (false); - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly ManualResetEventSlim _ansiResponseReady = new (false); private readonly CancellationTokenSource? _ansiRequestTokenSource = new (); private readonly ConcurrentQueue _requestQueue = new (); private readonly ConcurrentQueue _responseQueue = new (); @@ -55,7 +55,7 @@ public abstract class ConsoleDriver _responseQueue.Enqueue (request); - _waitAnsiResponse.Set (); + _ansiResponseReady.Set (); }; AnsiEscapeSequenceRequests.Add (ansiRequest); @@ -127,9 +127,9 @@ public abstract class ConsoleDriver try { - _waitAnsiResponse.Wait (_ansiRequestTokenSource!.Token); + _ansiResponseReady.Wait (_ansiRequestTokenSource!.Token); - _waitAnsiResponse.Reset (); + _ansiResponseReady.Reset (); _responseQueue.TryDequeue (out _); From c06c157bc005e29f6919310f85d388b2a619fabc Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Nov 2024 13:13:04 +0000 Subject: [PATCH 127/151] Tidying up layout. --- UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index a3d77d90b..80ffdb6ea 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -217,7 +217,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 }; w.Add (label, tfValue); - label = new () { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "_Terminator:" }; + label = new () { X = Pos.Left (tfValue) + label.Text.Length, Y = Pos.Top (tfValue) - 1, Text = "_Terminator:" }; var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 }; w.Add (label, tfTerminator); @@ -270,7 +270,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; w.Add (label, tvError); - label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "E_xpectedResponseValue:" }; + label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "_Value:" }; var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true }; w.Add (label, tvValue); From 1ea9ae0b65f9e0668ab2c6b599a20ba5bcdb7d4b Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Nov 2024 13:38:47 +0000 Subject: [PATCH 128/151] Handles error on request values. --- .../AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs | 11 ++++++++--- .../AnsiEscapeSequenceRequestUtils.cs | 4 ++-- .../ConsoleDrivers/CursesDriver/UnixMainLoop.cs | 2 +- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 2 +- .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 6 +++--- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs index 39a193b07..2027beab0 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs @@ -53,9 +53,9 @@ public class AnsiEscapeSequenceRequest /// public required string Terminator { get; init; } - internal void RaiseResponseFromInput (string? response) + internal void RaiseResponseFromInput (string? response, AnsiEscapeSequenceRequest? request) { - ProcessResponse (response); + ProcessResponse (response, request); ResponseFromInput?.Invoke (this, AnsiEscapeSequenceResponse); } @@ -69,7 +69,7 @@ public class AnsiEscapeSequenceRequest /// Process the of an ANSI escape sequence request. /// /// The response. - private void ProcessResponse (string? response) + private void ProcessResponse (string? response, AnsiEscapeSequenceRequest? request) { var error = new StringBuilder (); var values = new string? [] { null }; @@ -108,6 +108,11 @@ public class AnsiEscapeSequenceRequest { (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (response?.ToCharArray ()); } + + if (request is { } && !string.IsNullOrEmpty (request.ExpectedResponseValue) && request.ExpectedResponseValue != values [0]) + { + error.AppendLine ($"Error executing ANSI request:\nValue ends with '{values [0]}'\nand doesn't end with: '{ExpectedResponseValue! [^1]}'"); + } } AnsiEscapeSequenceResponse = new () diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs index bc2ff7232..6eef22931 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs @@ -347,7 +347,7 @@ public static class AnsiEscapeSequenceRequestUtils lock (seqReqStatus?.AnsiRequest._responseLock!) { - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString, seqReqStatus.AnsiRequest); } return; @@ -382,7 +382,7 @@ public static class AnsiEscapeSequenceRequestUtils { lock (result.AnsiRequest._responseLock) { - result.AnsiRequest.RaiseResponseFromInput (ToString (cki)); + result.AnsiRequest.RaiseResponseFromInput (ToString (cki), result.AnsiRequest); } } } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 96e7a89f7..aa9579f4d 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -264,7 +264,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); } } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index fc6120f8a..05f568667 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -102,7 +102,7 @@ internal class NetEvents : IDisposable { AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index afc3263f2..34dc8bd8e 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -120,7 +120,7 @@ internal class WindowsConsole { readingSequence = false; raisedResponse = true; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest); // Clear the terminator for not be enqueued inputRecord = default (InputRecord); // Clear numberEventsRead to not exit @@ -145,7 +145,7 @@ internal class WindowsConsole lock (seqReqStatus!.AnsiRequest._responseLock) { - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ()); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest); // Clear the terminator for not be enqueued inputRecord = default (InputRecord); } @@ -162,7 +162,7 @@ internal class WindowsConsole { AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); // Clear the terminator for not be enqueued inputRecord = default (InputRecord); } From 99350b7576a85b1a2bf6e27945777dbf8648ba01 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Nov 2024 21:31:31 +0000 Subject: [PATCH 129/151] Add local function to clear input. --- .../WindowsDriver/WindowsConsole.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 34dc8bd8e..69d0de755 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -121,10 +121,8 @@ internal class WindowsConsole readingSequence = false; raisedResponse = true; seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); - // Clear numberEventsRead to not exit - numberEventsRead = 0; + ClearInputRecord (); + // Clear the ansiSequence to avoid insert another Esc character ansiSequence.Clear (); } @@ -146,8 +144,7 @@ internal class WindowsConsole lock (seqReqStatus!.AnsiRequest._responseLock) { seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); + ClearInputRecord(); } _retries = 0; @@ -163,8 +160,7 @@ internal class WindowsConsole AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); + ClearInputRecord(); } } @@ -209,6 +205,15 @@ internal class WindowsConsole } return null; + + void ClearInputRecord () + { + // Clear the terminator for not be enqueued + inputRecord = default (InputRecord); + + // Clear numberEventsRead to not exit + numberEventsRead = 0; + } } internal bool _forceRead; From e30200db69a6311dc4c3fd9f5cbbfe4182884a5b Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Nov 2024 22:38:22 +0000 Subject: [PATCH 130/151] Remove duplicate wait. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 705f60e3a..d7ecb9ac7 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -68,8 +68,6 @@ public abstract class ConsoleDriver if (!_ansiRequestTokenSource.IsCancellationRequested) { _mainLoopDriver.WaitForInput.Set (); - - _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); } } catch (OperationCanceledException) From bea966978afa085c6f3358de58fceaee77b2c505 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 00:27:13 +0000 Subject: [PATCH 131/151] Restoring drivers with the sctructural changes. --- Terminal.Gui/Application/Application.Run.cs | 2 +- Terminal.Gui/Application/MainLoop.cs | 30 +- .../AnsiEscapeSequenceRequest.cs | 126 ---- .../AnsiEscapeSequenceRequestStatus.cs | 17 - .../AnsiEscapeSequenceRequests.cs | 80 --- .../AnsiEscapeSequenceResponse.cs | 64 -- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 125 ---- .../CursesDriver/CursesDriver.cs | 438 ++++++++++++-- .../CursesDriver/GetTIOCGWINSZ.c | 12 - .../CursesDriver/GetTIOCGWINSZ.sh | 17 - .../CursesDriver/UnixMainLoop.cs | 551 +++++------------- .../ConsoleDrivers/CursesDriver/binding.cs | 13 - .../ConsoleDrivers/CursesDriver/constants.cs | 2 + .../EscSeqUtils/EscSeqReqStatus.cs | 28 + .../EscSeqUtils/EscSeqRequests.cs | 114 ++++ .../EscSeqUtils.cs} | 55 +- .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 2 - .../ConsoleDrivers/NetDriver/NetDriver.cs | 39 +- .../ConsoleDrivers/NetDriver/NetEvents.cs | 173 +++--- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 36 +- .../WindowsDriver/WindowsConsole.cs | 141 +---- .../WindowsDriver/WindowsDriver.cs | 12 +- .../WindowsDriver/WindowsMainLoop.cs | 29 +- Terminal.Gui/Terminal.Gui.csproj | 45 +- .../compiled-binaries/libGetTIOCGWINSZ.dylib | Bin 16512 -> 0 bytes .../compiled-binaries/libGetTIOCGWINSZ.so | Bin 15160 -> 0 bytes .../Scenarios/AnsiEscapeSequenceRequests.cs | 440 -------------- UnitTests/Application/MainLoopTests.cs | 4 - .../Input/AnsiEscapeSequenceRequestsTests.cs | 79 --- UnitTests/Input/EscSeqRequestsTests.cs | 72 +++ ...questUtilsTests.cs => EscSeqUtilsTests.cs} | 409 +++++++------ UnitTests/View/Adornment/PaddingTests.cs | 2 - 32 files changed, 1158 insertions(+), 1999 deletions(-) delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c delete mode 100644 Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh create mode 100644 Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs create mode 100644 Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs rename Terminal.Gui/ConsoleDrivers/{AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs => EscSeqUtils/EscSeqUtils.cs} (97%) delete mode 100644 Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib delete mode 100644 Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.so delete mode 100644 UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs delete mode 100644 UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs create mode 100644 UnitTests/Input/EscSeqRequestsTests.cs rename UnitTests/Input/{AnsiEscapeSequenceRequestUtilsTests.cs => EscSeqUtilsTests.cs} (80%) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 7414054b5..5337fea0d 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -521,7 +521,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// The driver for the application /// The main loop. - public static MainLoop? MainLoop { get; private set; } + internal static MainLoop? MainLoop { get; private set; } /// /// Set to true to cause to be called after the first iteration. Set to false (the default) to diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs index 9ee974c7c..9d8c08d37 100644 --- a/Terminal.Gui/Application/MainLoop.cs +++ b/Terminal.Gui/Application/MainLoop.cs @@ -1,4 +1,5 @@ -// +#nullable enable +// // MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui // // Authors: @@ -10,7 +11,7 @@ using System.Collections.ObjectModel; namespace Terminal.Gui; /// Interface to create a platform specific driver. -public interface IMainLoopDriver +internal interface IMainLoopDriver { /// Must report whether there are any events pending, or even block waiting for events. /// true, if there were pending events, false otherwise. @@ -29,16 +30,6 @@ public interface IMainLoopDriver /// Wakes up the that might be waiting on input, must be thread safe. void Wakeup (); - - /// - /// Flag to force reading input instead of call . - /// - bool ForceRead { get; set; } - - /// - /// Switch for waiting for input or signaled to resume. - /// - ManualResetEventSlim WaitForInput { get; set; } } /// The MainLoop monitors timers and idle handlers. @@ -46,7 +37,7 @@ public interface IMainLoopDriver /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. /// -public class MainLoop : IDisposable +internal class MainLoop : IDisposable { internal List> _idleHandlers = new (); internal SortedList _timeouts = new (); @@ -82,7 +73,7 @@ public class MainLoop : IDisposable /// The current in use. /// The main loop driver. - public IMainLoopDriver MainLoopDriver { get; private set; } + internal IMainLoopDriver? MainLoopDriver { get; private set; } /// Used for unit tests. internal bool Running { get; set; } @@ -127,7 +118,7 @@ public class MainLoop : IDisposable _idleHandlers.Add (idleHandler); } - MainLoopDriver.Wakeup (); + MainLoopDriver?.Wakeup (); return idleHandler; } @@ -198,7 +189,7 @@ public class MainLoop : IDisposable /// You can use this method if you want to probe if events are pending. Typically used if you need to flush the /// input queue while still running some of your own code in your main thread. /// - internal bool EventsPending () { return MainLoopDriver.EventsPending (); } + internal bool EventsPending () { return MainLoopDriver!.EventsPending (); } /// Removes an idle handler added with from processing. /// A token returned by @@ -232,7 +223,7 @@ public class MainLoop : IDisposable { lock (_timeoutsLockToken) { - int idx = _timeouts.IndexOfValue (token as Timeout); + int idx = _timeouts.IndexOfValue ((token as Timeout)!); if (idx == -1) { @@ -277,7 +268,7 @@ public class MainLoop : IDisposable } } - MainLoopDriver.Iteration (); + MainLoopDriver?.Iteration (); bool runIdle; @@ -303,8 +294,7 @@ public class MainLoop : IDisposable /// Invoked when a new timeout is added. To be used in the case when /// is . /// - [CanBeNull] - internal event EventHandler TimeoutAdded; + internal event EventHandler? TimeoutAdded; /// Wakes up the that might be waiting on input. internal void Wakeup () { MainLoopDriver?.Wakeup (); } diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs deleted file mode 100644 index 2027beab0..000000000 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs +++ /dev/null @@ -1,126 +0,0 @@ -#nullable enable -namespace Terminal.Gui; - -/// -/// Describes an ongoing ANSI request sent to the console. -/// Send a request using which will return the response. -/// -public class AnsiEscapeSequenceRequest -{ - internal readonly object _responseLock = new (); // Per-instance lock - - /// - /// Gets the response received from the request. - /// - public AnsiEscapeSequenceResponse? AnsiEscapeSequenceResponse { get; internal set; } - - /// - /// The value expected in the response after the CSI e.g. - /// - /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value - /// - /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, - /// - /// will be "8". - /// - public string? ExpectedResponseValue { get; init; } - - /// - /// Gets the request string to send e.g. see - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// - public required string Request { get; init; } - - /// - /// - /// Gets the terminator that uniquely identifies the response received from - /// the console. e.g. for - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// the terminator is - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator - /// - /// . - /// - /// - /// After sending a request, the first response with matching terminator will be matched - /// to the oldest outstanding request. - /// - /// - public required string Terminator { get; init; } - - internal void RaiseResponseFromInput (string? response, AnsiEscapeSequenceRequest? request) - { - ProcessResponse (response, request); - - ResponseFromInput?.Invoke (this, AnsiEscapeSequenceResponse); - } - - /// - /// Raised with the response object and validation. - /// - internal event EventHandler? ResponseFromInput; - - /// - /// Process the of an ANSI escape sequence request. - /// - /// The response. - private void ProcessResponse (string? response, AnsiEscapeSequenceRequest? request) - { - var error = new StringBuilder (); - var values = new string? [] { null }; - - try - { - if (!string.IsNullOrEmpty (response) && !response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc)) - { - throw new InvalidOperationException ($"Invalid Response: {response}"); - } - - if (string.IsNullOrEmpty (Terminator)) - { - throw new InvalidOperationException ("Terminator request is empty."); - } - - if (string.IsNullOrEmpty (response)) - { - throw new InvalidOperationException ("Response request is null."); - } - - if (!string.IsNullOrEmpty (response) && !response.EndsWith (Terminator [^1])) - { - string resp = string.IsNullOrEmpty (response) ? "" : response.Last ().ToString (); - - throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{Terminator [^1]}'"); - } - } - catch (Exception ex) - { - error.AppendLine ($"Error executing ANSI request:\n{ex.Message}"); - } - finally - { - if (string.IsNullOrEmpty (error.ToString ())) - { - (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (response?.ToCharArray ()); - } - - if (request is { } && !string.IsNullOrEmpty (request.ExpectedResponseValue) && request.ExpectedResponseValue != values [0]) - { - error.AppendLine ($"Error executing ANSI request:\nValue ends with '{values [0]}'\nand doesn't end with: '{ExpectedResponseValue! [^1]}'"); - } - } - - AnsiEscapeSequenceResponse = new () - { - Response = response, Error = error.ToString (), - Terminator = string.IsNullOrEmpty (response) ? "" : response [^1].ToString (), - ExpectedResponseValue = values [0], - Valid = string.IsNullOrWhiteSpace (error.ToString ()) && !string.IsNullOrWhiteSpace (response) - }; - } -} diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs deleted file mode 100644 index b07d48236..000000000 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable enable -namespace Terminal.Gui; - -/// -/// Represents the status of an ANSI escape sequence request made to the terminal using -/// . -/// -/// -public class AnsiEscapeSequenceRequestStatus -{ - /// Creates a new state of escape sequence request. - /// The object. - public AnsiEscapeSequenceRequestStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; } - - /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). - public AnsiEscapeSequenceRequest AnsiRequest { get; } -} diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs deleted file mode 100644 index 62c0908fb..000000000 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs +++ /dev/null @@ -1,80 +0,0 @@ -#nullable enable - -using System.Collections.Concurrent; - -namespace Terminal.Gui; - -/// -/// Manages ANSI Escape Sequence requests and responses. The list of -/// contains the -/// status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator). -/// -public static class AnsiEscapeSequenceRequests -{ - /// - /// Adds a new request for the ANSI Escape Sequence defined by . Adds a - /// instance to list. - /// - /// The object. - public static void Add (AnsiEscapeSequenceRequest ansiRequest) - { - lock (ansiRequest._responseLock) - { - Statuses.Enqueue (new (ansiRequest)); - } - - System.Diagnostics.Debug.Assert (Statuses.Count > 0); - } - - /// - /// Clear the property. - /// - public static void Clear () - { - lock (Statuses) - { - Statuses.Clear (); - } - } - - /// - /// Indicates if a with the exists in the - /// list. - /// - /// - /// - /// if exist, otherwise. - public static bool HasResponse (string terminator, out AnsiEscapeSequenceRequestStatus? seqReqStatus) - { - lock (Statuses) - { - Statuses.TryPeek (out seqReqStatus); - - return seqReqStatus?.AnsiRequest.Terminator == terminator; - } - } - - /// - /// Removes a request defined by . If a matching - /// is - /// found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented. - /// If the number of outstanding requests is 0, the is removed from - /// . - /// - /// The object. - public static void Remove (AnsiEscapeSequenceRequestStatus? seqReqStatus) - { - lock (Statuses) - { - Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? request); - - if (request != seqReqStatus) - { - throw new InvalidOperationException ("Both EscSeqReqStatus objects aren't equals."); - } - } - } - - /// Gets the list. - public static ConcurrentQueue Statuses { get; } = new (); -} diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs deleted file mode 100644 index 104cd760a..000000000 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs +++ /dev/null @@ -1,64 +0,0 @@ -#nullable enable -namespace Terminal.Gui; - -/// -/// Describes a response received from the console as a result of a request being sent via -/// . -/// -public class AnsiEscapeSequenceResponse -{ - /// - /// Gets the error string received from e.g. see - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// . - /// - public required string Error { get; init; } - - /// - /// The value expected in the response after the CSI e.g. - /// - /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value - /// - /// should result in a response of the form ESC [ 8 ; height ; width t. In this case, - /// - /// will be "8". - /// - - public string? ExpectedResponseValue { get; init; } - - /// - /// Gets the Response string received from e.g. see - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// . - /// - public required string? Response { get; init; } - - /// - /// - /// Gets the terminator that uniquely identifies the response received from - /// the console. e.g. for - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Request - /// - /// the terminator is - /// - /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator - /// - /// . - /// - /// - /// After sending a request, the first response with matching terminator will be matched - /// to the oldest outstanding request. - /// - /// - public required string Terminator { get; init; } - - /// - /// Gets if the request has a valid response. - /// - public bool Valid { get; internal set; } -} diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index d7ecb9ac7..9b57f20db 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1,6 +1,5 @@ #nullable enable -using System.Collections.Concurrent; using System.Diagnostics; namespace Terminal.Gui; @@ -13,77 +12,6 @@ namespace Terminal.Gui; /// public abstract class ConsoleDriver { - private readonly ManualResetEventSlim _waitAnsiRequest = new (false); - private readonly ManualResetEventSlim _ansiResponseReady = new (false); - private readonly CancellationTokenSource? _ansiRequestTokenSource = new (); - private readonly ConcurrentQueue _requestQueue = new (); - private readonly ConcurrentQueue _responseQueue = new (); - private IMainLoopDriver? _mainLoopDriver; - - internal void ProcessAnsiRequestHandler () - { - while (_ansiRequestTokenSource is { IsCancellationRequested: false}) - { - try - { - if (_requestQueue.Count == 0) - { - try - { - _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - _waitAnsiRequest.Reset (); - } - - while (_requestQueue.TryDequeue (out AnsiEscapeSequenceRequest? ansiRequest)) - { - try - { - lock (ansiRequest._responseLock) - { - AnsiEscapeSequenceRequest request = ansiRequest; - - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == request); - Debug.Assert (e == request.AnsiEscapeSequenceResponse); - - _responseQueue.Enqueue (request); - - _ansiResponseReady.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - _mainLoopDriver!.ForceRead = true; - } - - if (!_ansiRequestTokenSource.IsCancellationRequested) - { - _mainLoopDriver.WaitForInput.Set (); - } - } - catch (OperationCanceledException) - { - return; - } - - } - } - catch (OperationCanceledException) - { - return; - } - } - } - /// /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. /// @@ -104,59 +32,6 @@ public abstract class ConsoleDriver #region ANSI Esc Sequence Handling - /// - /// Provide unique handling for all the terminal write ANSI escape sequence request. - /// - /// The object. - /// The object. - /// if the request response is valid, otherwise. - public bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) - { - ArgumentNullException.ThrowIfNull (mainLoopDriver, nameof (mainLoopDriver)); - ArgumentNullException.ThrowIfNull (ansiRequest, nameof (ansiRequest)); - - lock (ansiRequest._responseLock) - { - _mainLoopDriver = mainLoopDriver; - _requestQueue.Enqueue (ansiRequest); - - _waitAnsiRequest.Set (); - } - - try - { - _ansiResponseReady.Wait (_ansiRequestTokenSource!.Token); - - _ansiResponseReady.Reset (); - - _responseQueue.TryDequeue (out _); - - lock (ansiRequest._responseLock) - { - _mainLoopDriver.ForceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - catch (OperationCanceledException) - { - return false; - } - } - // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API? /// diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 98cdbdba9..66a783a6a 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -104,7 +104,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); } } @@ -112,7 +112,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); + Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); } } @@ -150,7 +150,7 @@ internal class CursesDriver : ConsoleDriver } else { - _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + _mainLoopDriver?.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); } } } @@ -299,7 +299,7 @@ internal class CursesDriver : ConsoleDriver redrawAttr = attr; output.Append ( - AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB ( + EscSeqUtils.CSI_SetForegroundColorRGB ( attr.Foreground.R, attr.Foreground.G, attr.Foreground.B @@ -307,7 +307,7 @@ internal class CursesDriver : ConsoleDriver ); output.Append ( - AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB ( + EscSeqUtils.CSI_SetBackgroundColorRGB ( attr.Background.R, attr.Background.G, attr.Background.B @@ -554,11 +554,11 @@ internal class CursesDriver : ConsoleDriver if (visibility != CursorVisibility.Invisible) { _mainLoopDriver?.WriteRaw ( - AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( - (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style) - (((int)visibility >> 24) - & 0xFF) - ) + EscSeqUtils.CSI_SetCursorStyle ( + (EscSeqUtils.DECSCUSR_Style) + (((int)visibility >> 24) + & 0xFF) + ) ); } @@ -571,7 +571,7 @@ internal class CursesDriver : ConsoleDriver { // + 1 is needed because non-Windows is based on 1 instead of 0 and // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (row + 1, col + 1)); + Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); return true; } @@ -580,6 +580,7 @@ internal class CursesDriver : ConsoleDriver private Curses.Window? _window; private UnixMainLoop? _mainLoopDriver; + private object _processInputToken; internal override MainLoop Init () { @@ -638,6 +639,17 @@ internal class CursesDriver : ConsoleDriver { Curses.timeout (0); } + + _processInputToken = _mainLoopDriver.AddWatch ( + 0, + UnixMainLoop.Condition.PollIn, + x => + { + ProcessInput (); + + return true; + } + ); } CurrentAttribute = new (ColorName16.White, ColorName16.Black); @@ -678,59 +690,415 @@ internal class CursesDriver : ConsoleDriver Curses.refresh (); } - Task.Run (ProcessAnsiRequestHandler); + EscSeqUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed; } return new (_mainLoopDriver); } - internal void ProcessInput (UnixMainLoop.PollData inputEvent) + internal void ProcessInput () { - switch (inputEvent.EventType) + int wch; + int code = Curses.get_wch (out wch); + + //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}"); + if (code == Curses.ERR) { - case UnixMainLoop.EventType.Key: - ConsoleKeyInfo consoleKeyInfo = inputEvent.KeyEvent; + return; + } - KeyCode map = AnsiEscapeSequenceRequestUtils.MapKey (consoleKeyInfo); + var k = KeyCode.Null; - if (map == KeyCode.Null) + if (code == Curses.KEY_CODE_YES) + { + while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) + { + ProcessWinChange (); + code = Curses.get_wch (out wch); + } + + if (wch == 0) + { + return; + } + + if (wch == Curses.KeyMouse) + { + int wch2 = wch; + + while (wch2 == Curses.KeyMouse) { - break; + Key kea = null; + + ConsoleKeyInfo [] cki = + { + new ((char)KeyCode.Esc, 0, false, false, false), + new ('[', 0, false, false, false), + new ('<', 0, false, false, false) + }; + code = 0; + HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki); } - OnKeyDown (new (map)); - OnKeyUp (new (map)); + return; + } - break; - case UnixMainLoop.EventType.Mouse: - var me = new MouseEventArgs { Position = inputEvent.MouseEvent.Position, Flags = inputEvent.MouseEvent.MouseFlags }; - OnMouseEvent (me); + k = MapCursesKey (wch); - break; - case UnixMainLoop.EventType.WindowSize: - Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height); - ProcessWinChange (size); + if (wch >= 277 && wch <= 288) + { + // Shift+(F1 - F12) + wch -= 12; + k = KeyCode.ShiftMask | MapCursesKey (wch); + } + else if (wch >= 289 && wch <= 300) + { + // Ctrl+(F1 - F12) + wch -= 24; + k = KeyCode.CtrlMask | MapCursesKey (wch); + } + else if (wch >= 301 && wch <= 312) + { + // Ctrl+Shift+(F1 - F12) + wch -= 36; + k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch); + } + else if (wch >= 313 && wch <= 324) + { + // Alt+(F1 - F12) + wch -= 48; + k = KeyCode.AltMask | MapCursesKey (wch); + } + else if (wch >= 325 && wch <= 327) + { + // Shift+Alt+(F1 - F3) + wch -= 60; + k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch); + } - break; - default: - throw new ArgumentOutOfRangeException (); + OnKeyDown (new Key (k)); + OnKeyUp (new Key (k)); + + return; + } + + // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey + if (wch == 27) + { + Curses.timeout (10); + + code = Curses.get_wch (out int wch2); + + if (code == Curses.KEY_CODE_YES) + { + k = KeyCode.AltMask | MapCursesKey (wch); + } + + Key key = null; + + if (code == 0) + { + // The ESC-number handling, debatable. + // Simulates the AltMask itself by pressing Alt + Space. + // Needed for macOS + if (wch2 == (int)KeyCode.Space) + { + k = KeyCode.AltMask | KeyCode.Space; + } + else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A + && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z) + { + k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space)); + } + else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64) + { + k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64)); + } + else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) + { + k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0)); + } + else + { + ConsoleKeyInfo [] cki = + [ + new ((char)KeyCode.Esc, 0, false, false, false), new ((char)wch2, 0, false, false, false) + ]; + HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); + + return; + } + //else if (wch2 == Curses.KeyCSI) + //{ + // ConsoleKeyInfo [] cki = + // { + // new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false) + // }; + // HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); + + // return; + //} + //else + //{ + // // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. + // if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) + // { + // k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask)); + // } + + // if (wch2 == 0) + // { + // k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space; + // } + // //else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) + // //{ + // // k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space; + // //} + // else if (wch2 < 256) + // { + // k = (KeyCode)wch2; // | KeyCode.AltMask; + // } + // else + // { + // k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2); + // } + //} + + key = new Key (k); + } + else + { + key = Key.Esc; + } + + OnKeyDown (key); + OnKeyUp (key); + } + else if (wch == Curses.KeyTab) + { + k = MapCursesKey (wch); + OnKeyDown (new Key (k)); + OnKeyUp (new Key (k)); + } + else if (wch == 127) + { + // Backspace needed for macOS + k = KeyCode.Backspace; + OnKeyDown (new Key (k)); + OnKeyUp (new Key (k)); + } + else + { + // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. + k = (KeyCode)wch; + + if (wch == 0) + { + k = KeyCode.CtrlMask | KeyCode.Space; + } + else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64) + { + if ((KeyCode)(wch + 64) != KeyCode.J) + { + k = KeyCode.CtrlMask | (KeyCode)(wch + 64); + } + } + else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) + { + k = (KeyCode)wch | KeyCode.ShiftMask; + } + + if (wch == '\n' || wch == '\r') + { + k = KeyCode.Enter; + } + + // Strip the KeyCode.Space flag off if it's set + //if (k != KeyCode.Space && k.HasFlag (KeyCode.Space)) + if (Key.GetIsKeyCodeAtoZ (k) && (k & KeyCode.Space) != 0) + { + k &= ~KeyCode.Space; + } + + OnKeyDown (new Key (k)); + OnKeyUp (new Key (k)); } } - private void ProcessWinChange (Size size) + + internal void ProcessWinChange () { - if (!RunningUnitTests && Curses.ChangeWindowSize (size.Height, size.Width)) + if (!RunningUnitTests && Curses.CheckWinChange ()) { ClearContents (); - OnSizeChanged (new (new (Cols, Rows))); + OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); + } + } + + private void HandleEscSeqResponse ( + ref int code, + ref KeyCode k, + ref int wch2, + ref Key keyEventArgs, + ref ConsoleKeyInfo [] cki + ) + { + ConsoleKey ck = 0; + ConsoleModifiers mod = 0; + + while (code == 0) + { + code = Curses.get_wch (out wch2); + var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false); + + if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) + { + EscSeqUtils.DecodeEscSeq ( + ref consoleKeyInfo, + ref ck, + cki, + ref mod, + out _, + out _, + out _, + out _, + out bool isKeyMouse, + out List mouseFlags, + out Point pos, + out _, + EscSeqUtils.ProcessMouseEvent + ); + + if (isKeyMouse) + { + foreach (MouseFlags mf in mouseFlags) + { + OnMouseEvent (new () { Flags = mf, Position = pos }); + } + + cki = null; + + if (wch2 == 27) + { + cki = EscSeqUtils.ResizeArray ( + new ConsoleKeyInfo ( + (char)KeyCode.Esc, + 0, + false, + false, + false + ), + cki + ); + } + } + else + { + k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); + keyEventArgs = new Key (k); + OnKeyDown (keyEventArgs); + } + } + else + { + cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki); + } + } + } + + private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e) + { + OnMouseEvent (e); + } + + private static KeyCode MapCursesKey (int cursesKey) + { + switch (cursesKey) + { + case Curses.KeyF1: return KeyCode.F1; + case Curses.KeyF2: return KeyCode.F2; + case Curses.KeyF3: return KeyCode.F3; + case Curses.KeyF4: return KeyCode.F4; + case Curses.KeyF5: return KeyCode.F5; + case Curses.KeyF6: return KeyCode.F6; + case Curses.KeyF7: return KeyCode.F7; + case Curses.KeyF8: return KeyCode.F8; + case Curses.KeyF9: return KeyCode.F9; + case Curses.KeyF10: return KeyCode.F10; + case Curses.KeyF11: return KeyCode.F11; + case Curses.KeyF12: return KeyCode.F12; + case Curses.KeyUp: return KeyCode.CursorUp; + case Curses.KeyDown: return KeyCode.CursorDown; + case Curses.KeyLeft: return KeyCode.CursorLeft; + case Curses.KeyRight: return KeyCode.CursorRight; + case Curses.KeyHome: return KeyCode.Home; + case Curses.KeyEnd: return KeyCode.End; + case Curses.KeyNPage: return KeyCode.PageDown; + case Curses.KeyPPage: return KeyCode.PageUp; + case Curses.KeyDeleteChar: return KeyCode.Delete; + case Curses.KeyInsertChar: return KeyCode.Insert; + case Curses.KeyTab: return KeyCode.Tab; + case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask; + case Curses.KeyBackspace: return KeyCode.Backspace; + case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask; + case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask; + case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask; + case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask; + case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask; + case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask; + case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask; + case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask; + case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask; + case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask; + case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask; + case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask; + case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask; + case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask; + case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask; + case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask; + case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask; + case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask; + case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask; + case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask; + case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask; + case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask; + case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask; + case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask; + case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask; + case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask; + case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask; + case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask; + case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask; + default: return KeyCode.Null; } } internal override void End () { + EscSeqUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; StopReportingMouseMoves (); SetCursorVisibility (CursorVisibility.Default); + if (_mainLoopDriver is { }) + { + _mainLoopDriver.RemoveWatch (_processInputToken); + } + if (RunningUnitTests) { return; diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c deleted file mode 100644 index 6ca9471d2..000000000 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -// Used to get the value of the TIOCGWINSZ variable, -// which may have different values ​​on different Unix operating systems. -// Linux=0x005413 -// Darwin and OpenBSD=0x40087468, -// Solaris=0x005468 -// See https://stackoverflow.com/questions/16237137/what-is-termios-tiocgwinsz -int get_tiocgwinsz_value() { - return TIOCGWINSZ; -} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh b/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh deleted file mode 100644 index e94706eb1..000000000 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Create output directory if it doesn't exist -mkdir -p ../../compiled-binaries - -# Determine the output file extension based on the OS -if [[ "$OSTYPE" == "linux-gnu"* ]]; then - OUTPUT_FILE="../../compiled-binaries/libGetTIOCGWINSZ.so" -elif [[ "$OSTYPE" == "darwin"* ]]; then - OUTPUT_FILE="../../compiled-binaries/libGetTIOCGWINSZ.dylib" -else - echo "Unsupported OS: $OSTYPE" - exit 1 -fi - -# Compile the C file -gcc -shared -fPIC -o "$OUTPUT_FILE" GetTIOCGWINSZ.c diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index aa9579f4d..c6414a2b9 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -3,7 +3,6 @@ // mainloop.cs: Linux/Curses MainLoop implementation. // -using System.Collections.Concurrent; using System.Runtime.InteropServices; namespace Terminal.Gui; @@ -13,7 +12,7 @@ namespace Terminal.Gui; /// In addition to the general functions of the MainLoop, the Unix version can watch file descriptors using the /// AddWatch methods. /// -internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver +internal class UnixMainLoop : IMainLoopDriver { /// Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions. [Flags] @@ -38,17 +37,31 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver PollNval = 32 } - private readonly CursesDriver _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); - private MainLoop? _mainLoop; - private Pollfd []? _pollMap; - private readonly ConcurrentQueue _pollDataQueue = new (); - private readonly ManualResetEventSlim _eventReady = new (false); - ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false); - private readonly ManualResetEventSlim _windowSizeChange = new (false); - private readonly CancellationTokenSource _eventReadyTokenSource = new (); - private readonly CancellationTokenSource _inputHandlerTokenSource = new (); + public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff); + private static readonly nint _ignore = Marshal.AllocHGlobal (1); - void IMainLoopDriver.Wakeup () { _eventReady.Set (); } + private readonly CursesDriver _cursesDriver; + private readonly Dictionary _descriptorWatchers = new (); + private readonly int [] _wakeUpPipes = new int [2]; + private MainLoop? _mainLoop; + private bool _pollDirty = true; + private Pollfd []? _pollMap; + private bool _winChanged; + + public UnixMainLoop (ConsoleDriver consoleDriver) + { + ArgumentNullException.ThrowIfNull (consoleDriver); + + _cursesDriver = (CursesDriver)consoleDriver; + } + + void IMainLoopDriver.Wakeup () + { + if (!ConsoleDriver.RunningUnitTests) + { + write (_wakeUpPipes [1], _ignore, 1); + } + } void IMainLoopDriver.Setup (MainLoop mainLoop) { @@ -61,381 +74,169 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver try { - // Setup poll for stdin (fd 0) - _pollMap = new Pollfd [1]; - _pollMap [0].fd = 0; // stdin (file descriptor 0) - _pollMap [0].events = (short)Condition.PollIn; // Monitor input for reading + pipe (_wakeUpPipes); + + AddWatch ( + _wakeUpPipes [0], + Condition.PollIn, + _ => + { + read (_wakeUpPipes [0], _ignore, 1); + + return true; + } + ); } catch (DllNotFoundException e) { throw new NotSupportedException ("libc not found", e); } - - AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed; - - Task.Run (CursesInputHandler, _inputHandlerTokenSource.Token); - Task.Run (WindowSizeHandler, _inputHandlerTokenSource.Token); - } - - private static readonly int TIOCGWINSZ = GetTIOCGWINSZValue (); - - private const string PlaceholderLibrary = "compiled-binaries/libGetTIOCGWINSZ"; // Placeholder, won't directly load - - [DllImport (PlaceholderLibrary, EntryPoint = "get_tiocgwinsz_value")] - private static extern int GetTIOCGWINSZValueInternal (); - - public static int GetTIOCGWINSZValue () - { - // Determine the correct library path based on the OS - string libraryPath = Path.Combine ( - AppContext.BaseDirectory, - "compiled-binaries", - RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ? "libGetTIOCGWINSZ.dylib" : "libGetTIOCGWINSZ.so"); - - // Load the native library manually - nint handle = NativeLibrary.Load (libraryPath); - - // Ensure the handle is valid - if (handle == nint.Zero) - { - throw new DllNotFoundException ($"Unable to load library: {libraryPath}"); - } - - return GetTIOCGWINSZValueInternal (); - } - - private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e) - { - _pollDataQueue.Enqueue (EnqueueMouseEvent (e.Flags, e.Position)); - } - - private void WindowSizeHandler () - { - var ws = new Winsize (); - ioctl (0, TIOCGWINSZ, ref ws); - - // Store initial window size - int rows = ws.ws_row; - int cols = ws.ws_col; - - while (_inputHandlerTokenSource is { IsCancellationRequested: false }) - { - try - { - _windowSizeChange.Wait (_inputHandlerTokenSource.Token); - _windowSizeChange.Reset (); - - while (!_inputHandlerTokenSource.IsCancellationRequested) - { - // Wait for a while then check if screen has changed sizes - Task.Delay (500, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); - - ioctl (0, TIOCGWINSZ, ref ws); - - if (rows != ws.ws_row || cols != ws.ws_col) - { - rows = ws.ws_row; - cols = ws.ws_col; - - _pollDataQueue.Enqueue (EnqueueWindowSizeEvent (rows, cols)); - - break; - } - } - } - catch (OperationCanceledException) - { - return; - } - - _eventReady.Set (); - } - } - - bool IMainLoopDriver.ForceRead { get; set; } - private int _retries; - - private void CursesInputHandler () - { - while (_mainLoop is { }) - { - try - { - if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead) - { - try - { - ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token); - } - catch (Exception ex) - { - if (ex is OperationCanceledException or ObjectDisposedException) - { - return; - } - - throw; - } - - ((IMainLoopDriver)this).WaitForInput.Reset (); - } - - ProcessInputQueue (); - } - catch (OperationCanceledException) - { - return; - } - } - } - - private void ProcessInputQueue () - { - if (_pollDataQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead) - { - while (!_inputHandlerTokenSource.IsCancellationRequested) - { - try - { - Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - int n = poll (_pollMap!, (uint)_pollMap!.Length, 0); - - if (n > 0) - { - // Check if stdin has data - if ((_pollMap [0].revents & (int)Condition.PollIn) != 0) - { - // Allocate memory for the buffer - var buf = new byte [2048]; - nint bufPtr = Marshal.AllocHGlobal (buf.Length); - - try - { - // Read from the stdin - int bytesRead = read (_pollMap [0].fd, bufPtr, buf.Length); - - if (bytesRead > 0) - { - // Copy the data from unmanaged memory to a byte array - var buffer = new byte [bytesRead]; - Marshal.Copy (bufPtr, buffer, 0, bytesRead); - - // Convert the byte array to a string (assuming UTF-8 encoding) - string data = Encoding.UTF8.GetString (buffer); - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) - { - data = data.Insert (0, AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos)); - AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; - } - - // Enqueue the data - ProcessEnqueuePollData (data); - } - } - finally - { - // Free the allocated memory - Marshal.FreeHGlobal (bufPtr); - } - } - - if (_retries > 0) - { - _retries = 0; - } - - break; - } - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - if (_retries > 1) - { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus)) - { - lock (seqReqStatus.AnsiRequest._responseLock) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } - } - } - - if (_pollDataQueue.Count > 0) - { - _eventReady.Set (); - } - } - - private void ProcessEnqueuePollData (string pollData) - { - foreach (string split in AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (pollData)) - { - EnqueuePollData (split); - } - } - - private void EnqueuePollData (string pollDataPart) - { - ConsoleKeyInfo [] cki = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (pollDataPart); - - ConsoleKey key = 0; - ConsoleModifiers mod = 0; - ConsoleKeyInfo newConsoleKeyInfo = default; - - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( - ref newConsoleKeyInfo, - ref key, - cki, - ref mod, - out string c1Control, - out string code, - out string [] values, - out string terminating, - out bool isMouse, - out List mouseFlags, - out Point pos, - out AnsiEscapeSequenceRequestStatus? seqReqStatus, - AnsiEscapeSequenceRequestUtils.ProcessMouseEvent - ); - - if (isMouse) - { - foreach (MouseFlags mf in mouseFlags) - { - _pollDataQueue.Enqueue (EnqueueMouseEvent (mf, pos)); - } - - return; - } - - if (newConsoleKeyInfo != default) - { - _pollDataQueue.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo)); - } - } - - private PollData EnqueueMouseEvent (MouseFlags mouseFlags, Point pos) - { - var mouseEvent = new MouseEvent { Position = pos, MouseFlags = mouseFlags }; - - return new () { EventType = EventType.Mouse, MouseEvent = mouseEvent }; - } - - private PollData EnqueueKeyboardEvent (ConsoleKeyInfo keyInfo) - { - return new () { EventType = EventType.Key, KeyEvent = keyInfo }; - } - - private PollData EnqueueWindowSizeEvent (int rows, int cols) - { - return new () { EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (cols, rows) } }; } bool IMainLoopDriver.EventsPending () { - ((IMainLoopDriver)this).WaitForInput.Set (); - _windowSizeChange.Set (); - - if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (ConsoleDriver.RunningUnitTests) { return true; } - try + UpdatePollMap (); + + bool checkTimersResult = _mainLoop!.CheckTimersAndIdleHandlers (out int pollTimeout); + + int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout); + + if (n == KEY_RESIZE) { - if (!_eventReadyTokenSource.IsCancellationRequested) - { - _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return true; - } - finally - { - _eventReady.Reset (); + _winChanged = true; } - if (!_eventReadyTokenSource.IsCancellationRequested) - { - return _pollDataQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); - } - - return true; + return checkTimersResult || n >= KEY_RESIZE; } void IMainLoopDriver.Iteration () { - // Dequeue and process the data - while (_pollDataQueue.TryDequeue (out PollData inputRecords)) + if (ConsoleDriver.RunningUnitTests) { - _cursesDriver.ProcessInput (inputRecords); + return; + } + + if (_winChanged) + { + _winChanged = false; + _cursesDriver.ProcessInput (); + + // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426 + _cursesDriver.ProcessWinChange (); + } + + if (_pollMap is null) + { + return; + } + + foreach (Pollfd p in _pollMap) + { + if (p.revents == 0) + { + continue; + } + + if (!_descriptorWatchers.TryGetValue (p.fd, out Watch? watch)) + { + continue; + } + + if (!watch.Callback (_mainLoop!)) + { + _descriptorWatchers.Remove (p.fd); + } } } void IMainLoopDriver.TearDown () { - AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; - - _inputHandlerTokenSource.Cancel (); - _inputHandlerTokenSource.Dispose (); - ((IMainLoopDriver)this).WaitForInput?.Dispose (); - - _windowSizeChange.Dispose(); - - _pollDataQueue.Clear (); - - _eventReadyTokenSource.Cancel (); - _eventReadyTokenSource.Dispose (); - _eventReady.Dispose (); + _descriptorWatchers.Clear (); _mainLoop = null; } + /// Watches a file descriptor for activity. + /// + /// When the condition is met, the provided callback is invoked. If the callback returns false, the watch is + /// automatically removed. The return value is a token that represents this watch, you can use this token to remove the + /// watch by calling RemoveWatch. + /// + internal object AddWatch (int fileDescriptor, Condition condition, Func callback) + { + ArgumentNullException.ThrowIfNull (callback); + + var watch = new Watch { Condition = condition, Callback = callback, File = fileDescriptor }; + _descriptorWatchers [fileDescriptor] = watch; + _pollDirty = true; + + return watch; + } + + /// Removes an active watch from the mainloop. + /// The token parameter is the value returned from AddWatch + internal void RemoveWatch (object token) + { + if (!ConsoleDriver.RunningUnitTests) + { + if (token is not Watch watch) + { + return; + } + + _descriptorWatchers.Remove (watch.File); + } + } + + private void UpdatePollMap () + { + if (!_pollDirty) + { + return; + } + + _pollDirty = false; + + _pollMap = new Pollfd [_descriptorWatchers.Count]; + var i = 0; + + foreach (int fd in _descriptorWatchers.Keys) + { + _pollMap [i].fd = fd; + _pollMap [i].events = (short)_descriptorWatchers [fd].Condition; + i++; + } + } + internal void WriteRaw (string ansiRequest) { // Write to stdout (fd 1) write (STDOUT_FILENO, ansiRequest, ansiRequest.Length); } + [DllImport ("libc")] + private static extern int pipe ([In][Out] int [] pipes); + [DllImport ("libc")] private static extern int poll ([In] [Out] Pollfd [] ufds, uint nfds, int timeout); [DllImport ("libc")] private static extern int read (int fd, nint buf, nint n); + [DllImport ("libc")] + private static extern int write (int fd, nint buf, nint n); + // File descriptor for stdout private const int STDOUT_FILENO = 1; [DllImport ("libc")] private static extern int write (int fd, string buf, int n); - [DllImport ("libc", SetLastError = true)] - private static extern int ioctl (int fd, int request, ref Winsize ws); - [StructLayout (LayoutKind.Sequential)] private struct Pollfd { @@ -444,74 +245,10 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver public readonly short revents; } - /// - /// Window or terminal size structure. This information is stored by the kernel in order to provide a consistent - /// interface, but is not used by the kernel. - /// - [StructLayout (LayoutKind.Sequential)] - public struct Winsize + private class Watch { - public ushort ws_row; // Number of rows - public ushort ws_col; // Number of columns - public ushort ws_xpixel; // Width in pixels (unused) - public ushort ws_ypixel; // Height in pixels (unused) + public Func Callback; + public Condition Condition; + public int File; } - - #region Events - - public enum EventType - { - Key = 1, - Mouse = 2, - WindowSize = 3 - } - - public struct MouseEvent - { - public Point Position; - public MouseFlags MouseFlags; - } - - public struct WindowSizeEvent - { - public Size Size; - } - - public struct PollData - { - public EventType EventType; - public ConsoleKeyInfo KeyEvent; - public MouseEvent MouseEvent; - public WindowSizeEvent WindowSizeEvent; - - public readonly override string ToString () - { - return (EventType switch - { - EventType.Key => ToString (KeyEvent), - EventType.Mouse => MouseEvent.ToString (), - EventType.WindowSize => WindowSizeEvent.ToString (), - _ => "Unknown event type: " + EventType - })!; - } - - /// Prints a ConsoleKeyInfoEx structure - /// - /// - public readonly string ToString (ConsoleKeyInfo cki) - { - var ke = new Key ((KeyCode)cki.KeyChar); - var sb = new StringBuilder (); - sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})"); - sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); - sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); - sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); - sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) "); - string s = sb.ToString ().TrimEnd (',').TrimEnd (' '); - - return $"[ConsoleKeyInfo({s})]"; - } - } - - #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index d79119401..16caaa05c 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -143,19 +143,6 @@ public partial class Curses return false; } - public static bool ChangeWindowSize (int l, int c) - { - if (l != lines || c != cols) - { - lines = l; - cols = c; - - return true; - } - - return false; - } - public static int clearok (nint win, bool bf) { return methods.clearok (win, bf); } public static int COLOR_PAIRS () { return methods.COLOR_PAIRS (); } public static int curs_set (int visibility) { return methods.curs_set (visibility); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs index 2984147a2..5700b779f 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs @@ -55,6 +55,8 @@ public partial class Curses public const int COLOR_GRAY = 0x8; public const int KEY_CODE_YES = 0x100; public const int ERR = unchecked ((int)0xffffffff); + public const int TIOCGWINSZ = 0x5413; + public const int TIOCGWINSZ_MAC = 0x40087468; [Flags] public enum Event : long { diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs new file mode 100644 index 000000000..6674de715 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs @@ -0,0 +1,28 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// Represents the status of an ANSI escape sequence request made to the terminal using +/// . +/// +/// +public class EscSeqReqStatus +{ + /// Creates a new state of escape sequence request. + /// The terminator. + /// The number of requests. + public EscSeqReqStatus (string terminator, int numReq) + { + Terminator = terminator; + NumRequests = NumOutstanding = numReq; + } + + /// Gets the number of unfinished requests. + public int NumOutstanding { get; set; } + + /// Gets the number of requests. + public int NumRequests { get; } + + /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). + public string Terminator { get; } +} diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs new file mode 100644 index 000000000..25925adeb --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs @@ -0,0 +1,114 @@ +#nullable enable + +using System.Collections.Concurrent; + +namespace Terminal.Gui; + +/// +/// Manages ANSI Escape Sequence requests and responses. The list of +/// contains the +/// status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator). +/// +public static class EscSeqRequests +{ + /// Gets the list. + public static List Statuses { get; } = new (); + + /// + /// Adds a new request for the ANSI Escape Sequence defined by . Adds a + /// instance to list. + /// + /// The terminator. + /// The number of requests. + public static void Add (string terminator, int numReq = 1) + { + lock (Statuses) + { + EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator); + + if (found is null) + { + Statuses.Add (new EscSeqReqStatus (terminator, numReq)); + } + else if (found.NumOutstanding < found.NumRequests) + { + found.NumOutstanding = Math.Min (found.NumOutstanding + numReq, found.NumRequests); + } + } + } + + /// + /// Clear the property. + /// + public static void Clear () + { + lock (Statuses) + { + Statuses.Clear (); + } + } + + /// + /// Indicates if a with the exists in the + /// list. + /// + /// + /// if exist, otherwise. + public static bool HasResponse (string terminator) + { + lock (Statuses) + { + EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator); + + if (found is null) + { + return false; + } + + if (found is { NumOutstanding: > 0 }) + { + return true; + } + + // BUGBUG: Why does an API that returns a bool remove the entry from the list? + // NetDriver and Unit tests never exercise this line of code. Maybe Curses does? + Statuses.Remove (found); + + return false; + } + } + + /// + /// Removes a request defined by . If a matching is + /// found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented. + /// If the number of outstanding requests is 0, the is removed from + /// . + /// + /// The terminating string. + public static void Remove (string terminator) + { + lock (Statuses) + { + EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator); + + if (found is null) + { + return; + } + + if (found.NumOutstanding == 0) + { + Statuses.Remove (found); + } + else if (found.NumOutstanding > 0) + { + found.NumOutstanding--; + + if (found.NumOutstanding == 0) + { + Statuses.Remove (found); + } + } + } + } +} diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs similarity index 97% rename from Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs rename to Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 6eef22931..7a8a67e36 100644 --- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -20,7 +20,7 @@ namespace Terminal.Gui; /// * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html /// * https://vt100.net/ /// -public static class AnsiEscapeSequenceRequestUtils +public static class EscSeqUtils { // TODO: One type per file - Move this enum to a separate file. /// @@ -189,7 +189,7 @@ public static class AnsiEscapeSequenceRequestUtils /// Indicates if the escape sequence is a mouse event. /// The button state. /// The position. - /// The object. + /// Indicates if the escape sequence is a response to a request. /// The handler that will process the event. public static void DecodeEscSeq ( ref ConsoleKeyInfo newConsoleKeyInfo, @@ -203,7 +203,7 @@ public static class AnsiEscapeSequenceRequestUtils out bool isMouse, out List buttonState, out Point pos, - out AnsiEscapeSequenceRequestStatus? seqReqStatus, + out bool isResponse, Action continuousButtonPressedHandler ) { @@ -212,7 +212,7 @@ public static class AnsiEscapeSequenceRequestUtils isMouse = false; buttonState = [0]; pos = default (Point); - seqReqStatus = null; + isResponse = false; var keyChar = '\0'; switch (c1Control) @@ -339,16 +339,10 @@ public static class AnsiEscapeSequenceRequestUtils return; } - if (AnsiEscapeSequenceRequests.HasResponse (terminator, out seqReqStatus)) + if (EscSeqRequests.HasResponse (terminator)) { - AnsiEscapeSequenceRequests.Remove (seqReqStatus); - - var ckiString = ToString (cki); - - lock (seqReqStatus?.AnsiRequest._responseLock!) - { - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString, seqReqStatus.AnsiRequest); - } + isResponse = true; + EscSeqRequests.Remove (terminator); return; } @@ -376,15 +370,10 @@ public static class AnsiEscapeSequenceRequestUtils else { // It's request response that wasn't handled by a valid request terminator - System.Diagnostics.Debug.Assert (AnsiEscapeSequenceRequests.Statuses.Count > 0); + System.Diagnostics.Debug.Assert (EscSeqRequests.Statuses.Count > 0); - if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result)) - { - lock (result.AnsiRequest._responseLock) - { - result.AnsiRequest.RaiseResponseFromInput (ToString (cki), result.AnsiRequest); - } - } + isResponse = true; + EscSeqRequests.Remove (terminator); } } else @@ -1804,7 +1793,12 @@ public static class AnsiEscapeSequenceRequestUtils /// https://terminalguide.namepad.de/seq/csi_sn__p-6/ /// The terminal reply to . ESC [ ? (y) ; (x) ; 1 R /// - public static readonly AnsiEscapeSequenceRequest CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" }; + public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n"; + + /// + /// The terminal reply to . ESC [ ? (y) ; (x) R + /// + public static readonly string CSI_RequestCursorPositionReport_Terminator = "R"; /// /// ESC [ 0 c - Send Device Attributes (Primary DA) @@ -1826,7 +1820,7 @@ public static class AnsiEscapeSequenceRequestUtils /// The terminator indicating a reply to or /// /// - public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" }; + public static readonly string CSI_SendDeviceAttributes = CSI + "0c"; /// /// ESC [ > 0 c - Send Device Attributes (Secondary DA) @@ -1834,7 +1828,7 @@ public static class AnsiEscapeSequenceRequestUtils /// The terminator indicating a reply to or /// /// - public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" }; + public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c"; /* TODO: depends on https://github.com/gui-cs/Terminal.Gui/pull/3768 @@ -1854,7 +1848,18 @@ public static class AnsiEscapeSequenceRequestUtils /// https://terminalguide.namepad.de/seq/csi_st-18/ /// The terminator indicating a reply to : ESC [ 8 ; height ; width t /// - public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", ExpectedResponseValue = "8" }; + public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t"; + + /// + /// The terminator indicating a reply to : ESC [ 8 ; height ; width t + /// + public static readonly string CSI_ReportTerminalSizeInChars_Terminator = "t"; + + /// + /// The value of the response to indicating value 1 and 2 are the terminal + /// size in chars. + /// + public static readonly string CSI_ReportTerminalSizeInChars_ResponseValue = "8"; #endregion } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index e6ea9cab0..d90caace7 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -3,8 +3,6 @@ internal class FakeMainLoop : IMainLoopDriver { public Action MockKeyPressed; - public bool ForceRead { get; set; } - public ManualResetEventSlim WaitForInput { get; set; } = new (); public FakeMainLoop (ConsoleDriver consoleDriver = null) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 91746a218..c477f27e6 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -29,15 +29,15 @@ internal class NetDriver : ConsoleDriver Console.Clear (); //Disable alternative screen buffer. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); //Set cursor key to cursor. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_ShowCursor); + Console.Out.Write (EscSeqUtils.CSI_ShowCursor); Platform.Suspend (); //Enable alternative screen buffer. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); SetContentsAsDirty (); Refresh (); @@ -131,7 +131,7 @@ internal class NetDriver : ConsoleDriver if (Force16Colors) { output.Append ( - AnsiEscapeSequenceRequestUtils.CSI_SetGraphicsRendition ( + EscSeqUtils.CSI_SetGraphicsRendition ( MapColors ( (ConsoleColor)attr.Background .GetClosestNamedColor16 (), @@ -146,7 +146,7 @@ internal class NetDriver : ConsoleDriver else { output.Append ( - AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB ( + EscSeqUtils.CSI_SetForegroundColorRGB ( attr.Foreground.R, attr.Foreground.G, attr.Foreground.B @@ -154,7 +154,7 @@ internal class NetDriver : ConsoleDriver ); output.Append ( - AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB ( + EscSeqUtils.CSI_SetBackgroundColorRGB ( attr.Background.R, attr.Background.G, attr.Background.B @@ -272,10 +272,10 @@ internal class NetDriver : ConsoleDriver Rows = Console.WindowHeight; //Enable alternative screen buffer. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); //Set cursor key to application. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_HideCursor); + Console.Out.Write (EscSeqUtils.CSI_HideCursor); } else { @@ -294,11 +294,6 @@ internal class NetDriver : ConsoleDriver _mainLoopDriver = new (this); _mainLoopDriver.ProcessInput = ProcessInput; - if (!RunningUnitTests) - { - Task.Run (ProcessAnsiRequestHandler); - } - return new (_mainLoopDriver); } @@ -315,7 +310,7 @@ internal class NetDriver : ConsoleDriver //Debug.WriteLine ($"event: {inputEvent}"); - KeyCode map = AnsiEscapeSequenceRequestUtils.MapKey (consoleKeyInfo); + KeyCode map = EscSeqUtils.MapKey (consoleKeyInfo); if (map == KeyCode.Null) { @@ -364,17 +359,15 @@ internal class NetDriver : ConsoleDriver StopReportingMouseMoves (); - - if (!RunningUnitTests) { Console.ResetColor (); //Disable alternative screen buffer. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); //Set cursor key to cursor. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_ShowCursor); + Console.Out.Write (EscSeqUtils.CSI_ShowCursor); Console.Out.Close (); } } @@ -464,7 +457,7 @@ internal class NetDriver : ConsoleDriver // + 1 is needed because non-Windows is based on 1 instead of 0 and // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (row + 1, col + 1)); + Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); return true; } @@ -493,7 +486,7 @@ internal class NetDriver : ConsoleDriver { _cachedCursorVisibility = visibility; - Console.Out.Write (visibility == CursorVisibility.Default ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor); + Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); return visibility == CursorVisibility.Default; } @@ -522,7 +515,7 @@ internal class NetDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); } } @@ -530,7 +523,7 @@ internal class NetDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); + Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); } } @@ -786,7 +779,7 @@ internal class NetDriver : ConsoleDriver } else { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetTerminalWindowSize (Rows, Cols)); + Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols)); } // CONCURRENCY: Unsynchronized access to Clip is not safe. diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 05f568667..d03a1fd9d 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -77,8 +77,6 @@ internal class NetEvents : IDisposable // The delay must be here because it may have a request response after a while // In WSL it takes longer for keys to be available. Task.Delay (100, cancellationToken).Wait (cancellationToken); - - ProcessResponse (); } cancellationToken.ThrowIfCancellationRequested (); @@ -86,39 +84,6 @@ internal class NetEvents : IDisposable return default (ConsoleKeyInfo); } - //internal bool _forceRead; - private int _retries; - - private void ProcessResponse () - { - if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - if (_retries > 1) - { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) - && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (seqReqStatus.AnsiRequest._responseLock) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } - } - private void ProcessInputQueue () { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) @@ -144,13 +109,13 @@ internal class NetEvents : IDisposable var ckiAlreadyResized = false; - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) + if (EscSeqUtils.IncompleteCkInfos is { }) { ckiAlreadyResized = true; - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); - AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki); + EscSeqUtils.IncompleteCkInfos = null; if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B') { @@ -163,7 +128,7 @@ internal class NetEvents : IDisposable { if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( + _cki = EscSeqUtils.ResizeArray ( new ( (char)KeyCode.Esc, 0, @@ -190,7 +155,6 @@ internal class NetEvents : IDisposable ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; _isEscSeq = false; - ProcessResponse (); ProcessMapConsoleKeyInfo (consoleKeyInfo); } @@ -200,7 +164,7 @@ internal class NetEvents : IDisposable if (!ckiAlreadyResized) { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); } if (Console.KeyAvailable) @@ -211,7 +175,6 @@ internal class NetEvents : IDisposable ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki!, ref mod); _cki = null; _isEscSeq = false; - ProcessResponse (); } break; @@ -221,11 +184,10 @@ internal class NetEvents : IDisposable { ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; - ProcessResponse (); if (Console.KeyAvailable) { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); } else { @@ -237,11 +199,6 @@ internal class NetEvents : IDisposable ProcessMapConsoleKeyInfo (consoleKeyInfo); - if (_retries > 0) - { - _retries = 0; - } - break; } @@ -261,7 +218,7 @@ internal class NetEvents : IDisposable _inputQueue.Enqueue ( new () { - EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo) + EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo) } ); _isEscSeq = false; @@ -357,7 +314,7 @@ internal class NetEvents : IDisposable ) { // isMouse is true if it's CSI<, false otherwise - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref newConsoleKeyInfo, ref key, cki, @@ -369,7 +326,7 @@ internal class NetEvents : IDisposable out bool isMouse, out List mouseFlags, out Point pos, - out AnsiEscapeSequenceRequestStatus? seqReqStatus, + out bool isReq, (f, p) => HandleMouseEvent (MapMouseFlags (f), p) ); @@ -383,9 +340,9 @@ internal class NetEvents : IDisposable return; } - if (seqReqStatus is { }) + if (isReq) { - //HandleRequestResponseEvent (c1Control, code, values, terminating); + HandleRequestResponseEvent (c1Control, code, values, terminating); return; } @@ -530,66 +487,66 @@ internal class NetEvents : IDisposable return mbs; } - //private Point _lastCursorPosition; + private Point _lastCursorPosition; - //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) - //{ - // if (terminating == + private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + { + if (terminating == - // // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. - // // The observation is correct because the response isn't immediate and this is useless - // EscSeqUtils.CSI_RequestCursorPositionReport.Terminator) - // { - // var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; + // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. + // The observation is correct because the response isn't immediate and this is useless + EscSeqUtils.CSI_RequestCursorPositionReport_Terminator) + { + var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; - // if (_lastCursorPosition.Y != point.Y) - // { - // _lastCursorPosition = point; - // var eventType = EventType.WindowPosition; - // var winPositionEv = new WindowPositionEvent { CursorPosition = point }; + if (_lastCursorPosition.Y != point.Y) + { + _lastCursorPosition = point; + var eventType = EventType.WindowPosition; + var winPositionEv = new WindowPositionEvent { CursorPosition = point }; - // _inputQueue.Enqueue ( - // new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } - // ); - // } - // else - // { - // return; - // } - // } - // else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator) - // { - // if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value) - // { - // EnqueueWindowSizeEvent ( - // Math.Max (int.Parse (values [1]), 0), - // Math.Max (int.Parse (values [2]), 0), - // Math.Max (int.Parse (values [1]), 0), - // Math.Max (int.Parse (values [2]), 0) - // ); - // } - // else - // { - // EnqueueRequestResponseEvent (c1Control, code, values, terminating); - // } - // } - // else - // { - // EnqueueRequestResponseEvent (c1Control, code, values, terminating); - // } + _inputQueue.Enqueue ( + new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } + ); + } + else + { + return; + } + } + else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator) + { + if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue) + { + EnqueueWindowSizeEvent ( + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0), + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0) + ); + } + else + { + EnqueueRequestResponseEvent (c1Control, code, values, terminating); + } + } + else + { + EnqueueRequestResponseEvent (c1Control, code, values, terminating); + } - // _inputReady.Set (); - //} + _inputReady.Set (); + } - //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) - //{ - // var eventType = EventType.RequestResponse; - // var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) }; + private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + { + var eventType = EventType.RequestResponse; + var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) }; - // _inputQueue.Enqueue ( - // new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv } - // ); - //} + _inputQueue.Enqueue ( + new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv } + ); + } private void HandleMouseEvent (MouseButtonState buttonState, Point pos) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 9ffc1298e..08efe66d5 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -19,10 +19,9 @@ internal class NetMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _eventReady = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); + private readonly ManualResetEventSlim _waitForProbe = new (false); private readonly ConcurrentQueue _resultQueue = new (); private MainLoop? _mainLoop; - bool IMainLoopDriver.ForceRead { get; set; } - ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false); /// Initializes the class with the console driver. /// Passing a consoleDriver is provided to capture windows resizing. @@ -52,7 +51,12 @@ internal class NetMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - ((IMainLoopDriver)this).WaitForInput.Set (); + if (ConsoleDriver.RunningUnitTests) + { + return true; + } + + _waitForProbe.Set (); if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { @@ -66,13 +70,6 @@ internal class NetMainLoop : IMainLoopDriver // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there // are no timers, but there IS an idle handler waiting. _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); - - _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); - - if (!_eventReadyTokenSource.IsCancellationRequested) - { - return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); - } } } catch (OperationCanceledException) @@ -84,13 +81,20 @@ internal class NetMainLoop : IMainLoopDriver _eventReady.Reset (); } + _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); + + if (!_eventReadyTokenSource.IsCancellationRequested) + { + return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); + } + // If cancellation was requested then always return true return true; } void IMainLoopDriver.Iteration () { - while (_resultQueue.TryDequeue (out NetEvents.InputResult inputRecords)) + while (!ConsoleDriver.RunningUnitTests && _resultQueue.TryDequeue (out NetEvents.InputResult inputRecords)) { ProcessInput?.Invoke (inputRecords); } @@ -104,7 +108,7 @@ internal class NetMainLoop : IMainLoopDriver _eventReadyTokenSource.Dispose (); _eventReady.Dispose (); - ((IMainLoopDriver)this).WaitForInput.Dispose (); + _waitForProbe.Dispose (); _resultQueue.Clear (); _netEvents?.Dispose (); @@ -119,11 +123,11 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested) { try { - ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token); + _waitForProbe.Wait (_inputHandlerTokenSource.Token); } catch (Exception ex) { @@ -135,7 +139,7 @@ internal class NetMainLoop : IMainLoopDriver throw; } - ((IMainLoopDriver)this).WaitForInput.Reset (); + _waitForProbe.Reset (); } ProcessInputQueue (); @@ -149,7 +153,7 @@ internal class NetMainLoop : IMainLoopDriver private void ProcessInputQueue () { - if (_resultQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead) + if (_resultQueue.Count == 0) { NetEvents.InputResult? result = _netEvents!.DequeueInput (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 69d0de755..2c6689737 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -62,9 +62,6 @@ internal class WindowsConsole const int BUFFER_SIZE = 1; InputRecord inputRecord = default; uint numberEventsRead = 0; - StringBuilder ansiSequence = new StringBuilder (); - bool readingSequence = false; - bool raisedResponse = false; while (!_inputReadyCancellationTokenSource!.IsCancellationRequested) { @@ -79,101 +76,6 @@ internal class WindowsConsole out inputRecord, BUFFER_SIZE, out numberEventsRead); - - if (inputRecord.EventType == EventType.Key) - { - KeyEventRecord keyEvent = inputRecord.KeyEvent; - - if (keyEvent.bKeyDown) - { - char inputChar = keyEvent.UnicodeChar; - - // Check if input is part of an ANSI escape sequence - if (inputChar == '\u001B') // Escape character - { - // Peek to check if there is any input available with key event and bKeyDown and not Escape character - if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0) - { - if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true, KeyEvent.UnicodeChar: not '\u001B' }) - { - // It's really an ANSI request response - readingSequence = true; - // Start a new sequence ensuring in the cases where wasn't clear by reading the terminator - ansiSequence.Clear (); - ansiSequence.Append (inputChar); - - continue; - } - } - } - else if (readingSequence) - { - ansiSequence.Append (inputChar); - - // Check if the sequence has ended with an expected command terminator - if (AnsiEscapeSequenceRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus)) - { - // Finished reading the sequence and remove the enqueued request - AnsiEscapeSequenceRequests.Remove (seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) - { - readingSequence = false; - raisedResponse = true; - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest); - ClearInputRecord (); - - // Clear the ansiSequence to avoid insert another Esc character - ansiSequence.Clear (); - } - } - - if (readingSequence) - { - continue; - } - } - } - } - } - - if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus); - - lock (seqReqStatus!.AnsiRequest._responseLock) - { - seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest); - ClearInputRecord(); - } - - _retries = 0; - } - else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - if (_retries > 1) - { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (seqReqStatus.AnsiRequest._responseLock) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest); - ClearInputRecord(); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; } if (numberEventsRead > 0) @@ -181,16 +83,13 @@ internal class WindowsConsole return inputRecord; } - if (!_forceRead) + try { - try - { - Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - return null; - } + Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return null; } } catch (Exception ex) @@ -205,26 +104,15 @@ internal class WindowsConsole } return null; - - void ClearInputRecord () - { - // Clear the terminator for not be enqueued - inputRecord = default (InputRecord); - - // Clear numberEventsRead to not exit - numberEventsRead = 0; - } } - internal bool _forceRead; - private void ProcessInputQueue () { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { try { - if (_inputQueue.Count == 0 || _forceRead) + if (_inputQueue.Count == 0) { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { @@ -253,7 +141,6 @@ internal class WindowsConsole } } - private CharInfo []? _originalStdOutChars; public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) @@ -288,8 +175,8 @@ internal class WindowsConsole { _stringBuilder.Clear (); - _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorPosition); - _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (0, 0)); + _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); Attribute? prev = null; @@ -300,8 +187,8 @@ internal class WindowsConsole if (attr != prev) { prev = attr; - _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); - _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); + _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); + _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); } if (info.Char != '\x1b') @@ -317,8 +204,8 @@ internal class WindowsConsole } } - _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorPosition); - _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_HideCursor); + _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_HideCursor); var s = _stringBuilder.ToString (); @@ -1118,8 +1005,6 @@ internal class WindowsConsole } } - private int _retries; - #if false // Not needed on the constructor. Perhaps could be used on resizing. To study. [DllImport ("kernel32.dll", ExactSpelling = true)] static extern IntPtr GetConsoleWindow (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 51df2c4d8..f18616fd2 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -244,7 +244,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); + sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); WinConsole?.WriteANSI (sb.ToString ()); } @@ -280,7 +280,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (visibility != CursorVisibility.Invisible ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor); + sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } } @@ -295,7 +295,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor); + sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } @@ -420,7 +420,7 @@ internal class WindowsDriver : ConsoleDriver if (!RunningUnitTests && _isWindowsTerminal) { // Disable alternative screen buffer. - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); } } @@ -446,7 +446,7 @@ internal class WindowsDriver : ConsoleDriver if (_isWindowsTerminal) { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); } } catch (Win32Exception e) @@ -481,8 +481,6 @@ internal class WindowsDriver : ConsoleDriver if (!RunningUnitTests) { WinConsole?.SetInitialCursorVisibility (); - - Task.Run (ProcessAnsiRequestHandler); } return new MainLoop (_mainLoopDriver); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 13c8421fd..1cbe88211 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -23,7 +23,7 @@ internal class WindowsMainLoop : IMainLoopDriver // The records that we keep fetching private readonly ConcurrentQueue _resultQueue = new (); - ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false); + private readonly ManualResetEventSlim _waitForProbe = new (false); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -59,7 +59,12 @@ internal class WindowsMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - ((IMainLoopDriver)this).WaitForInput.Set (); + if (ConsoleDriver.RunningUnitTests) + { + return true; + } + + _waitForProbe.Set (); #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif @@ -83,7 +88,10 @@ internal class WindowsMainLoop : IMainLoopDriver } finally { - _eventReady.Reset (); + if (!_eventReadyTokenSource.IsCancellationRequested) + { + _eventReady.Reset (); + } } if (!_eventReadyTokenSource.IsCancellationRequested) @@ -104,7 +112,7 @@ internal class WindowsMainLoop : IMainLoopDriver void IMainLoopDriver.Iteration () { - while (_resultQueue.TryDequeue (out WindowsConsole.InputRecord inputRecords)) + while (!ConsoleDriver.RunningUnitTests && _resultQueue.TryDequeue (out WindowsConsole.InputRecord inputRecords)) { ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords); } @@ -133,7 +141,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - ((IMainLoopDriver)this).WaitForInput?.Dispose (); + _waitForProbe.Dispose (); _resultQueue.Clear (); @@ -148,19 +156,17 @@ internal class WindowsMainLoop : IMainLoopDriver _mainLoop = null; } - public bool ForceRead { get; set; } - private void WindowsInputHandler () { while (_mainLoop is { }) { try { - if (_inputHandlerTokenSource.IsCancellationRequested && !ForceRead) + if (_inputHandlerTokenSource.IsCancellationRequested) { try { - ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token); + _waitForProbe.Wait (_inputHandlerTokenSource.Token); } catch (Exception ex) { @@ -172,7 +178,7 @@ internal class WindowsMainLoop : IMainLoopDriver throw; } - ((IMainLoopDriver)this).WaitForInput.Reset (); + _waitForProbe.Reset (); } ProcessInputQueue (); @@ -187,7 +193,7 @@ internal class WindowsMainLoop : IMainLoopDriver private void ProcessInputQueue () { - if (_resultQueue?.Count == 0 || ForceRead) + if (_resultQueue?.Count == 0) { WindowsConsole.InputRecord? result = _winConsole!.DequeueInput (); @@ -232,4 +238,3 @@ internal class WindowsMainLoop : IMainLoopDriver } #endif } - diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 6d23904a0..007e5ee33 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -11,9 +11,9 @@ - - Terminal.Gui - + + Terminal.Gui + @@ -151,39 +151,22 @@ $(MSBuildThisFileDirectory)bin\$(Configuration)\ - - - + + + - - - - + + + + - - + + - - + + - - - - - - true - compiled-binaries/ - PreserveNewest - - - - - true - compiled-binaries/ - PreserveNewest - - \ No newline at end of file diff --git a/Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib b/Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib deleted file mode 100644 index 06c30292e9f9ec317f59a3dcddc432cba30f553b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16512 zcmeI3ziJdw6voeFH3^ttuu4#5Dn&87i%DS>2Q?&%5(B$p5ag0%C&|EON3yeO(qtP8 z8!PR%*9Wlo0W4Fc6l{C}g~jjOxfh2aV3+E<>^JAnoO@>PZ>BxpfBzayhy)c88Hc8! z;h4xfnSm!V2mOSMX)SNGG*ep9+3`iBRcK|GF`D(R>-*&qHD(S@S`K_e8d62}tS|{0BiL+MY>BE)hjkV^BYWol=kwuY#eFKq5 z9laJ@Tt^%t*0bm{HlqAFd$1ZSzjdg%%Kx!=?|=SP6lmWo>7VPL>PsI!e;m!9oQCOL z+5$U!D8h$O#TIFLPRU>LE0;S4>$371XAnw4iti_dDzS1{pthH0HtyR z(mv~v$RzA77wfsXNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L;5-RD zTYdlI>s~e)z5Hh9tFO6-7p=}y>%2T>#;yG9yT&??J%yb!b~ge)vTfb`62jbZ(o81|* z8ieo|3%DzKSgw#sK*G;vKc&TBYL@?6kw-*@tj1kIz9~Bhn7X%C9+vT@MFQ7EVT?N@ zxI==2+$r&p(IBlqN1o?2C#WMW-&&cr3gc!37nW-T2gze_+x3H&`Y+?{6kM1`=okkQ z<2^_?=H*fwZu5YQ*XTY>37?Rn`h0Q@UNe2$hZNCsoaZu6CcypWqOvOCNDMlDVbtB&*L26movy?7qKo_&by zSE#iM=fr0AYuIdP`tu zuKAkL&Q#fDsr#Gn(ir%)<+?u; z2Y-wvURZGUR;3cp9QT^apCt}{6g}Z5DBu1anSUwY_&tf1cE$0e9rd*y0=(Gy4dY&1 z@CA#z&wDE<)oa|RREx@|TiT~M(Fu_h|E3>*`x`87x|n!=Hu1r^Er}1#&r3v3CO*ID zEvE$ik|0_>J<`%n;{ea&Pj$NFR;BeriG!W1xSM$X2XATOh0ZuBXLgs%GHFkl!k3>XFs z1D6K_k=66=Swok?zqMJj<#LQ<2gxy#7f4Q#PnZ19=$T? ztP~yfsjJ}^xk@DJRQdZQH++};{bD)K`m7Y4`ZegJPfM{WYJ%HDU(CVO_5_0J-hyppwHgT2Y_ zxMf4hS2sMhaiiU_=_WCUGt}x_`#WF#T&}e$#rM03>YZb9zu|8d`*_HHz1Xo{5Pv6i zBJ^JdD;8K#YuxSITNKtq;0^94kBjvO`#wLuSz&#_{wj6q&A-ug9rg1pQ->{Nn3MJL zyyeHYs_^}I&$nM)xh`ax5j*Y^>|ar*Nzp0{@%F7BKx|ux4CFZyO|+`<{(|>6>O`m# zUBzGtu4Q9GpxmCR?yGp^^R?2h^oTnwMqPNCpTYB%QCmFL5#Q^mXvnvUA=>Pu{D3j1IHv5> zWR8*r(km#t;AR{)D%+>P9?y98Xx>w{=T6Wo-xOuLV?zH}iXhMeNN^v5Nc1V6lT9aS z+`PvbX*kDols#N5Dw|e9mTqvJCHkLCyzlU5y=)_YJLhACvSJ^AnU51y;~SmxLA-1y zP*oT8{-C`B<6}RA!N1LWhq40!B!kcSvd4QFM5$G*3Ve7t7?kNp!8`z7b# zMsMqy_9$sQH{jc3qL9-P9iBhN=W{BY6Z>`L^G-hZ@LV8iDm*}cKZLgg2O0YK45wF5 zgzy~#fc!T+FU0Ek#{>X5gNcd^=Q|L>PkXYwpty~ZA zACjXQkLQ3k!6Pn=hdx6cj=}t`Sm3c5;CZa-_9-cBlkkcQ`1KV_&;h<$V(nCi?v#WN ti}M _sends = new (); - - private readonly object _lockAnswers = new (); - private readonly Dictionary _answers = new (); - private readonly Dictionary _errors = new (); - - private GraphView _graphView; - - private ScatterSeries _sentSeries; - private ScatterSeries _answeredSeries; - private Label _lblSummary; - private Label _lblErrorSummary; - - public override void Main () - { - // Init - Application.Init (); - - var tv = new TabView - { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - - var single = new Tab (); - single.DisplayText = "_Single"; - single.View = BuildSingleTab (); - - Tab bulk = new (); - bulk.DisplayText = "_Multi"; - bulk.View = BuildBulkTab (); - - tv.AddTab (single, true); - tv.AddTab (bulk, false); - - // Setup - Create a top-level application window and configure it. - Window appWindow = new () - { - Title = GetQuitKeyAndName () - }; - - appWindow.Add (tv); - - // Run - Start the application. - Application.Run (appWindow); - bulk.View.Dispose (); - single.View.Dispose (); - appWindow.Dispose (); - - // Shutdown - Calling Application.Shutdown is required. - Application.Shutdown (); - } - - private View BuildBulkTab () - { - var w = new View - { - Width = Dim.Fill (), - Height = Dim.Fill (), - CanFocus = true - }; - - var lbl = new Label - { - Text = - "_This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.", - Height = 2, - Width = Dim.Fill () - }; - - Application.AddTimeout ( - TimeSpan.FromMilliseconds (1000), - () => - { - lock (_lockAnswers) - { - UpdateGraph (); - - UpdateResponses (); - } - - return true; - }); - - var tv = new TextView - { - Y = Pos.Bottom (lbl), - Width = Dim.Percent (50), - Height = Dim.Fill () - }; - - var lblDar = new Label - { - Y = Pos.Bottom (lbl), - X = Pos.Right (tv) + 1, - Text = "_DAR per second: " - }; - - var cbDar = new NumericUpDown - { - X = Pos.Right (lblDar), - Y = Pos.Bottom (lbl), - Value = 0 - }; - - cbDar.ValueChanging += (s, e) => - { - if (e.NewValue is < 0 or > 20) - { - e.Cancel = true; - } - }; - w.Add (cbDar); - - int lastSendTime = Environment.TickCount; - var lockObj = new object (); - int interval = 50; - - Application.AddTimeout ( - TimeSpan.FromMilliseconds ((double)interval / (cbDar.Value > 0 ? cbDar.Value : 1)), - () => - { - lock (lockObj) - { - if (cbDar.Value > 0) - { - interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds - int currentTime = Environment.TickCount; // Current system time in milliseconds - - // Check if the time elapsed since the last send is greater than the interval - if (currentTime - lastSendTime >= interval) - { - SendDar (); // Send the request - lastSendTime = currentTime; // Update the last send time - } - } - } - - return true; - }); - - _graphView = new () - { - Y = Pos.Bottom (cbDar), - X = Pos.Right (tv), - Width = Dim.Fill (), - Height = Dim.Fill (2) - }; - - _lblSummary = new () - { - Y = Pos.Bottom (_graphView), - X = Pos.Right (tv), - Width = Dim.Fill () - }; - - _lblErrorSummary = new () - { - Y = Pos.Bottom (_lblSummary), - X = Pos.Right (tv), - Width = Dim.Fill () - }; - - SetupGraph (); - - w.Add (lbl); - w.Add (lblDar); - w.Add (cbDar); - w.Add (tv); - w.Add (_graphView); - w.Add (_lblSummary); - w.Add (_lblErrorSummary); - - return w; - } - - private View BuildSingleTab () - { - var w = new View - { - Width = Dim.Fill (), - Height = Dim.Fill (), - CanFocus = true - }; - - w.Padding.Thickness = new (1); - - // TODO: This hackery is why I think the EscSeqUtils class should be refactored and the CSI's made type safe. - List scrRequests = new () - { - "CSI_SendDeviceAttributes", - "CSI_ReportTerminalSizeInChars", - "CSI_RequestCursorPositionReport", - "CSI_SendDeviceAttributes2" - }; - - var cbRequests = new ComboBox { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) }; - w.Add (cbRequests); - - // TODO: Use Pos.Align and Dim.Func so these hardcoded widths aren't needed. - var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "_Request:" }; - var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 }; - w.Add (label, tfRequest); - - label = new () { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "E_xpectedResponseValue:" }; - var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 }; - w.Add (label, tfValue); - - label = new () { X = Pos.Left (tfValue) + label.Text.Length, Y = Pos.Top (tfValue) - 1, Text = "_Terminator:" }; - var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 }; - w.Add (label, tfTerminator); - - cbRequests.SelectedItemChanged += (s, e) => - { - if (cbRequests.SelectedItem == -1) - { - return; - } - - string selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem]; - AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null; - - switch (selAnsiEscapeSequenceRequestName) - { - case "CSI_SendDeviceAttributes": - selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; - - break; - case "CSI_ReportTerminalSizeInChars": - selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_ReportTerminalSizeInChars; - - break; - case "CSI_RequestCursorPositionReport": - selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_RequestCursorPositionReport; - - break; - case "CSI_SendDeviceAttributes2": - selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes2; - - break; - } - - tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : ""; - - tfValue.Text = selAnsiEscapeSequenceRequest is { } - ? selAnsiEscapeSequenceRequest.ExpectedResponseValue ?? "" - : ""; - tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : ""; - }; - - // Forces raise cbRequests.SelectedItemChanged to update TextFields - cbRequests.SelectedItem = 0; - - label = new () { Y = Pos.Bottom (tfRequest) + 2, Text = "_Response:" }; - var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; - w.Add (label, tvResponse); - - label = new () { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "_Error:" }; - var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true }; - w.Add (label, tvError); - - label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "_Value:" }; - var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true }; - w.Add (label, tvValue); - - label = new () { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "_Terminator:" }; - var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true }; - w.Add (label, tvTerminator); - - var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "_Send Request", IsDefault = true }; - - var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 }; - w.Add (lblSuccess); - - btnResponse.Accepting += (s, e) => - { - var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest - { - Request = tfRequest.Text, - Terminator = tfTerminator.Text, - ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text - }; - - bool success = Application.Driver!.TryWriteAnsiRequest ( - Application.MainLoop!.MainLoopDriver, - ref ansiEscapeSequenceRequest - ); - - tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? ""; - tvError.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Error ?? ""; - tvValue.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.ExpectedResponseValue ?? ""; - tvTerminator.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Terminator ?? ""; - - if (success) - { - lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"]; - lblSuccess.Text = "Success"; - } - else - { - lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"]; - lblSuccess.Text = "Error"; - } - }; - w.Add (btnResponse); - - w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "Send other requests by editing the TextFields." }); - - return w; - } - - private string GetSummary () - { - if (_answers.Count == 0) - { - return "No requests sent yet"; - } - - string last = _answers.Last ().Value.AnsiEscapeSequenceResponse!.Response; - - int unique = _answers.Values.Distinct ().Count (); - int total = _answers.Count; - - return $"Last:{last} U:{unique} T:{total}"; - } - - private string GetSummaryErrors () - { - if (_errors.Count == 0) - { - return "No errors received yet"; - } - - string last = _errors.Last ().Value.AnsiEscapeSequenceResponse!.Error; - - int unique = _errors.Values.Distinct ().Count (); - int total = _errors.Count; - - return $"Last:{last} U:{unique} T:{total}"; - } - - private void HandleResponse (AnsiEscapeSequenceRequest ansiRequest) - { - lock (_lockAnswers) - { - _answers.Add (DateTime.Now, ansiRequest); - KeyValuePair found = _sends.First (r => r.Value == ansiRequest); - _sends.Remove (found.Key); - } - } - - private void HandleResponseError (AnsiEscapeSequenceRequest ansiRequest) - { - lock (_lockAnswers) - { - _errors.Add (DateTime.Now, ansiRequest); - KeyValuePair found = _sends.First (r => r.Value == ansiRequest); - _sends.Remove (found.Key); - } - } - - private void SendDar () - { - AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; - _sends.Add (DateTime.Now, ansiRequest); - - if (Application.Driver!.TryWriteAnsiRequest (Application.MainLoop!.MainLoopDriver, ref ansiRequest)) - { - HandleResponse (ansiRequest); - } - else - { - HandleResponseError (ansiRequest); - } - } - - private void SetupGraph () - { - _graphView.Series.Add (_sentSeries = new ()); - _graphView.Series.Add (_answeredSeries = new ()); - - _sentSeries.Fill = new (new ('.'), new (ColorName16.BrightGreen, ColorName16.Black)); - _answeredSeries.Fill = new (new ('.'), new (ColorName16.BrightRed, ColorName16.Black)); - - // Todo: - // _graphView.Annotations.Add (_sentSeries new PathAnnotation {}); - - _graphView.CellSize = new (1, 1); - _graphView.MarginBottom = 2; - _graphView.AxisX.Increment = 1; - _graphView.AxisX.Text = "Seconds"; - _graphView.GraphColor = new Attribute (Color.Green, Color.Black); - } - - private static Func, int> ToSeconds () { return t => (int)(DateTime.Now - t.Key).TotalSeconds; } - - private void UpdateGraph () - { - System.Diagnostics.Debug.Assert (_sends.Count == 0); - - _sentSeries.Points = _sends - .GroupBy (ToSeconds ()) - .Select (g => new PointF (g.Key, g.Count ())) - .ToList (); - - _answeredSeries.Points = _answers - .Where ( - r => r.Value.AnsiEscapeSequenceResponse is { } - && !string.IsNullOrEmpty (r.Value?.AnsiEscapeSequenceResponse.Response)) - .GroupBy (ToSeconds ()) - .Select (g => new PointF (g.Key, g.Count ())) - .ToList (); - - // _graphView.ScrollOffset = new PointF(,0); - if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) - { - _graphView.SetNeedsDraw (); - } - } - - private void UpdateResponses () - { - _lblSummary.Text = GetSummary (); - _lblSummary.SetNeedsDraw (); - - _lblErrorSummary.Text = GetSummaryErrors (); - _lblErrorSummary.SetNeedsDraw (); - } -} diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index db78ebe3b..47812926d 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -946,14 +946,10 @@ public class MainLoopTests private class TestMainloop : IMainLoopDriver { private MainLoop mainLoop; - private bool _forceRead1; - private ManualResetEventSlim _waitForInput1; public bool EventsPending () { throw new NotImplementedException (); } public void Iteration () { throw new NotImplementedException (); } public void TearDown () { throw new NotImplementedException (); } public void Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; } public void Wakeup () { throw new NotImplementedException (); } - public bool ForceRead { get; set; } - public ManualResetEventSlim WaitForInput { get; set; } } } diff --git a/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs b/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs deleted file mode 100644 index 1658fb469..000000000 --- a/UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace Terminal.Gui.InputTests; - -public class AnsiEscapeSequenceRequestsTests -{ - [Fact] - public void Add_Tests () - { - AnsiEscapeSequenceRequests.Clear (); - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - Assert.Single (AnsiEscapeSequenceRequests.Statuses); - Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - Assert.Equal (2, AnsiEscapeSequenceRequests.Statuses.Count); - Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - - AnsiEscapeSequenceRequests.Clear (); - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - Assert.Equal (2, AnsiEscapeSequenceRequests.Statuses.Count); - Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - Assert.Equal (3, AnsiEscapeSequenceRequests.Statuses.Count); - Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - } - - [Fact] - public void Constructor_Defaults () - { - AnsiEscapeSequenceRequests.Clear (); - Assert.NotNull (AnsiEscapeSequenceRequests.Statuses); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); - } - - [Fact] - public void Remove_Tests () - { - AnsiEscapeSequenceRequests.Clear (); - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - AnsiEscapeSequenceRequests.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus); - AnsiEscapeSequenceRequests.Remove (seqReqStatus); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); - - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus); - AnsiEscapeSequenceRequests.Remove (seqReqStatus); - Assert.Single (AnsiEscapeSequenceRequests.Statuses); - Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); - - AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus); - AnsiEscapeSequenceRequests.Remove (seqReqStatus); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); - } - - [Fact] - public void Requested_Tests () - { - AnsiEscapeSequenceRequests.Clear (); - Assert.False (AnsiEscapeSequenceRequests.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus)); - Assert.Null (seqReqStatus); - - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); - Assert.False (AnsiEscapeSequenceRequests.HasResponse ("r", out seqReqStatus)); - Assert.NotNull (seqReqStatus); - Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator); - Assert.True (AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus)); - Assert.NotNull (seqReqStatus); - Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator); - } - - [Fact] - public void Request_Initialization_AnsiEscapeSequenceResponse_Is_Null () - { - AnsiEscapeSequenceRequest ansiRequest = new () { Request = "\u001b[0c", Terminator = "c"}; - Assert.Null (ansiRequest.AnsiEscapeSequenceResponse); - } -} diff --git a/UnitTests/Input/EscSeqRequestsTests.cs b/UnitTests/Input/EscSeqRequestsTests.cs new file mode 100644 index 000000000..fd6e0510b --- /dev/null +++ b/UnitTests/Input/EscSeqRequestsTests.cs @@ -0,0 +1,72 @@ +namespace Terminal.Gui.InputTests; + +public class EscSeqRequestsTests +{ + [Fact] + public void Add_Tests () + { + EscSeqRequests.Clear (); + EscSeqRequests.Add ("t"); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqRequests.Add ("t", 2); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqRequests.Clear (); + EscSeqRequests.Add ("t", 2); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (2, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqRequests.Add ("t", 3); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (2, EscSeqRequests.Statuses [^1].NumOutstanding); + } + + [Fact] + public void Constructor_Defaults () + { + EscSeqRequests.Clear (); + Assert.NotNull (EscSeqRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); + } + + [Fact] + public void Remove_Tests () + { + EscSeqRequests.Clear (); + EscSeqRequests.Add ("t"); + EscSeqRequests.Remove ("t"); + Assert.Empty (EscSeqRequests.Statuses); + + EscSeqRequests.Add ("t", 2); + EscSeqRequests.Remove ("t"); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqRequests.Remove ("t"); + Assert.Empty (EscSeqRequests.Statuses); + } + + [Fact] + public void Requested_Tests () + { + EscSeqRequests.Clear (); + Assert.False (EscSeqRequests.HasResponse ("t")); + + EscSeqRequests.Add ("t"); + Assert.False (EscSeqRequests.HasResponse ("r")); + Assert.True (EscSeqRequests.HasResponse ("t")); + } +} diff --git a/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs similarity index 80% rename from UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs rename to UnitTests/Input/EscSeqUtilsTests.cs index a9c8f5d05..88011b6a8 100644 --- a/UnitTests/Input/AnsiEscapeSequenceRequestUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -3,7 +3,7 @@ namespace Terminal.Gui.InputTests; -public class AnsiEscapeSequenceRequestUtilsTests +public class EscSeqUtilsTests { private bool _actionStarted; private MouseFlags _arg1; @@ -11,8 +11,7 @@ public class AnsiEscapeSequenceRequestUtilsTests private string _c1Control, _code, _terminating; private ConsoleKeyInfo [] _cki; private bool _isKeyMouse; - [CanBeNull] - private AnsiEscapeSequenceRequestStatus _seqReqStatus; + private bool _isResponse; private ConsoleKey _key; private ConsoleModifiers _mod; private List _mouseFlags; @@ -28,7 +27,7 @@ public class AnsiEscapeSequenceRequestUtilsTests _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false) }; var expectedCki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -40,10 +39,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.Escape, _key); Assert.Equal (0, (int)_mod); @@ -54,7 +53,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -62,7 +61,7 @@ public class AnsiEscapeSequenceRequestUtilsTests _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false), new ('\u0012', 0, false, false, false) }; expectedCki = new ('\u0012', ConsoleKey.R, false, true, true); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -74,10 +73,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.R, _key); Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); @@ -88,7 +87,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -96,7 +95,7 @@ public class AnsiEscapeSequenceRequestUtilsTests _cki = new ConsoleKeyInfo [] { new ('\u001b', 0, false, false, false), new ('r', 0, false, false, false) }; expectedCki = new ('r', ConsoleKey.R, false, true, false); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -108,10 +107,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.R, _key); Assert.Equal (ConsoleModifiers.Alt, _mod); @@ -122,7 +121,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -135,7 +134,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, false, false); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -147,10 +146,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (0, (int)_mod); @@ -162,7 +161,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -180,7 +179,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, false, false); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -192,10 +191,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift, _mod); @@ -208,7 +207,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -225,7 +224,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, true, false); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -237,10 +236,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Alt, _mod); @@ -253,7 +252,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -270,7 +269,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, true, false); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -282,10 +281,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, _mod); @@ -298,7 +297,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -315,7 +314,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, false, true); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -327,10 +326,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Control, _mod); @@ -343,7 +342,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -360,7 +359,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, false, true); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -372,10 +371,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, _mod); @@ -388,7 +387,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -405,7 +404,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, false, true, true); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -417,10 +416,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); @@ -433,7 +432,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -450,7 +449,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = new ('\0', ConsoleKey.F3, true, true, true); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -462,10 +461,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.F3, _key); Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, _mod); @@ -478,7 +477,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -498,7 +497,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = default (ConsoleKeyInfo); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -510,10 +509,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -527,7 +526,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.True (_isKeyMouse); Assert.Equal (new () { MouseFlags.Button1Pressed }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -547,7 +546,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = default (ConsoleKeyInfo); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -559,10 +558,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -581,7 +580,7 @@ public class AnsiEscapeSequenceRequestUtilsTests _mouseFlags ); Assert.Equal (new (1, 2), _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -601,7 +600,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = default (ConsoleKeyInfo); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -613,10 +612,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -630,7 +629,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.True (_isKeyMouse); Assert.Equal (new () { MouseFlags.Button1DoubleClicked }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); ClearAll (); @@ -648,7 +647,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = default (ConsoleKeyInfo); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -660,10 +659,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -677,7 +676,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.True (_isKeyMouse); Assert.Equal (new () { MouseFlags.Button1TripleClicked }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), WantContinuousButtonPressed = true }; var top = new Toplevel (); @@ -702,7 +701,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = default (ConsoleKeyInfo); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -714,10 +713,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -731,7 +730,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.True (_isKeyMouse); Assert.Equal (new () { MouseFlags.Button1Pressed }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Application.Iteration += (s, a) => { @@ -770,7 +769,7 @@ public class AnsiEscapeSequenceRequestUtilsTests }; expectedCki = default (ConsoleKeyInfo); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -782,10 +781,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -799,15 +798,15 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.True (_isKeyMouse); Assert.Equal (new () { MouseFlags.Button1Released }, _mouseFlags); Assert.Equal (new (1, 2), _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); ClearAll (); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); - AnsiEscapeSequenceRequests.Clear (); - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + Assert.Empty (EscSeqRequests.Statuses); + EscSeqRequests.Clear (); + EscSeqRequests.Add ("t"); _cki = new ConsoleKeyInfo [] { @@ -823,10 +822,10 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('t', 0, false, false, false) }; expectedCki = default (ConsoleKeyInfo); - Assert.Single (AnsiEscapeSequenceRequests.Statuses); - Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses.ToArray () [^1].Terminator); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -838,10 +837,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (0, (int)_key); Assert.Equal (0, (int)_mod); @@ -855,7 +854,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.NotNull (_seqReqStatus); + Assert.NotNull (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -889,7 +888,7 @@ public class AnsiEscapeSequenceRequestUtilsTests var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -901,10 +900,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (consoleKey, _key); @@ -933,7 +932,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -955,7 +954,7 @@ public class AnsiEscapeSequenceRequestUtilsTests ConsoleKeyInfo expectedCki = default; - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -967,10 +966,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Single (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.None, _key); Assert.Equal (ConsoleModifiers.None, _mod); @@ -981,13 +980,13 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal ([0], _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); - Assert.Equal (_cki, AnsiEscapeSequenceRequestUtils.IncompleteCkInfos); + Assert.Equal (_cki, EscSeqUtils.IncompleteCkInfos); - _cki = AnsiEscapeSequenceRequestUtils.InsertArray ( - AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, + _cki = EscSeqUtils.InsertArray ( + EscSeqUtils.IncompleteCkInfos, [ new ('0', 0, false, false, false), new (';', 0, false, false, false), @@ -999,9 +998,9 @@ public class AnsiEscapeSequenceRequestUtilsTests expectedCki = default; // Add a request to avoid assert failure in the DecodeEscSeq method - AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" }); + EscSeqRequests.Add ("t"); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -1013,10 +1012,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.None, _key); @@ -1028,12 +1027,12 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal ([0], _mouseFlags); Assert.Equal (Point.Empty, _pos); - AnsiEscapeSequenceRequests.HasResponse ("t", out _seqReqStatus); - Assert.Null (_seqReqStatus); + Assert.False (EscSeqRequests.HasResponse ("t")); + Assert.True (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); - Assert.NotEqual (_cki, AnsiEscapeSequenceRequestUtils.IncompleteCkInfos); - Assert.Contains (AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos), AnsiEscapeSequenceRequestUtils.ToString (_cki)); + Assert.NotEqual (_cki, EscSeqUtils.IncompleteCkInfos); + Assert.Contains (EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos), EscSeqUtils.ToString (_cki)); ClearAll (); } @@ -1054,7 +1053,7 @@ public class AnsiEscapeSequenceRequestUtilsTests _cki = [new (keyChar, 0, false, false, false)]; var expectedCki = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - AnsiEscapeSequenceRequestUtils.DecodeEscSeq ( + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, _cki, @@ -1066,10 +1065,10 @@ public class AnsiEscapeSequenceRequestUtilsTests out _isKeyMouse, out _mouseFlags, out _pos, - out _seqReqStatus, + out _isResponse, ProcessContinuousButtonPressed ); - Assert.Empty (AnsiEscapeSequenceRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (consoleKey, _key); @@ -1107,7 +1106,7 @@ public class AnsiEscapeSequenceRequestUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.Null (_seqReqStatus); + Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -1117,38 +1116,38 @@ public class AnsiEscapeSequenceRequestUtilsTests [Fact] public void Defaults_Values () { - Assert.Equal ('\x1b', AnsiEscapeSequenceRequestUtils.KeyEsc); - Assert.Equal ("\x1b[", AnsiEscapeSequenceRequestUtils.CSI); - Assert.Equal ("\x1b[?1003h", AnsiEscapeSequenceRequestUtils.CSI_EnableAnyEventMouse); - Assert.Equal ("\x1b[?1006h", AnsiEscapeSequenceRequestUtils.CSI_EnableSgrExtModeMouse); - Assert.Equal ("\x1b[?1015h", AnsiEscapeSequenceRequestUtils.CSI_EnableUrxvtExtModeMouse); - Assert.Equal ("\x1b[?1003l", AnsiEscapeSequenceRequestUtils.CSI_DisableAnyEventMouse); - Assert.Equal ("\x1b[?1006l", AnsiEscapeSequenceRequestUtils.CSI_DisableSgrExtModeMouse); - Assert.Equal ("\x1b[?1015l", AnsiEscapeSequenceRequestUtils.CSI_DisableUrxvtExtModeMouse); - Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); - Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); + Assert.Equal ('\x1b', EscSeqUtils.KeyEsc); + Assert.Equal ("\x1b[", EscSeqUtils.CSI); + Assert.Equal ("\x1b[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse); + Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse); + Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse); + Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse); + Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse); + Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse); + Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.CSI_EnableMouseEvents); + Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.CSI_DisableMouseEvents); } [Fact] public void GetC1ControlChar_Tests () { - Assert.Equal ("IND", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('D')); - Assert.Equal ("NEL", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('E')); - Assert.Equal ("HTS", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('H')); - Assert.Equal ("RI", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('M')); - Assert.Equal ("SS2", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('N')); - Assert.Equal ("SS3", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('O')); - Assert.Equal ("DCS", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('P')); - Assert.Equal ("SPA", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('V')); - Assert.Equal ("EPA", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('W')); - Assert.Equal ("SOS", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('X')); - Assert.Equal ("DECID", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('Z')); - Assert.Equal ("CSI", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('[')); - Assert.Equal ("ST", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('\\')); - Assert.Equal ("OSC", AnsiEscapeSequenceRequestUtils.GetC1ControlChar (']')); - Assert.Equal ("PM", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('^')); - Assert.Equal ("APC", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('_')); - Assert.Equal ("", AnsiEscapeSequenceRequestUtils.GetC1ControlChar ('\0')); + Assert.Equal ("IND", EscSeqUtils.GetC1ControlChar ('D')); + Assert.Equal ("NEL", EscSeqUtils.GetC1ControlChar ('E')); + Assert.Equal ("HTS", EscSeqUtils.GetC1ControlChar ('H')); + Assert.Equal ("RI", EscSeqUtils.GetC1ControlChar ('M')); + Assert.Equal ("SS2", EscSeqUtils.GetC1ControlChar ('N')); + Assert.Equal ("SS3", EscSeqUtils.GetC1ControlChar ('O')); + Assert.Equal ("DCS", EscSeqUtils.GetC1ControlChar ('P')); + Assert.Equal ("SPA", EscSeqUtils.GetC1ControlChar ('V')); + Assert.Equal ("EPA", EscSeqUtils.GetC1ControlChar ('W')); + Assert.Equal ("SOS", EscSeqUtils.GetC1ControlChar ('X')); + Assert.Equal ("DECID", EscSeqUtils.GetC1ControlChar ('Z')); + Assert.Equal ("CSI", EscSeqUtils.GetC1ControlChar ('[')); + Assert.Equal ("ST", EscSeqUtils.GetC1ControlChar ('\\')); + Assert.Equal ("OSC", EscSeqUtils.GetC1ControlChar (']')); + Assert.Equal ("PM", EscSeqUtils.GetC1ControlChar ('^')); + Assert.Equal ("APC", EscSeqUtils.GetC1ControlChar ('_')); + Assert.Equal ("", EscSeqUtils.GetC1ControlChar ('\0')); } [Fact] @@ -1156,51 +1155,51 @@ public class AnsiEscapeSequenceRequestUtilsTests { var cki = new ConsoleKeyInfo ('r', 0, false, false, false); var expectedCki = new ConsoleKeyInfo ('r', ConsoleKey.R, false, false, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, false, false); expectedCki = new ('r', ConsoleKey.R, true, false, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, true, false); expectedCki = new ('r', ConsoleKey.R, false, true, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, false, true); expectedCki = new ('r', ConsoleKey.R, false, false, true); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, true, false); expectedCki = new ('r', ConsoleKey.R, true, true, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, false, true, true); expectedCki = new ('r', ConsoleKey.R, false, true, true); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('r', 0, true, true, true); expectedCki = new ('r', ConsoleKey.R, true, true, true); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\u0012', 0, false, false, false); expectedCki = new ('\u0012', ConsoleKey.R, false, false, true); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\0', (ConsoleKey)64, false, false, true); expectedCki = new ('\0', ConsoleKey.Spacebar, false, false, true); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\r', 0, false, false, false); expectedCki = new ('\r', ConsoleKey.Enter, false, false, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('\u007f', 0, false, false, false); expectedCki = new ('\u007f', ConsoleKey.Backspace, false, false, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ('R', 0, false, false, false); expectedCki = new ('R', ConsoleKey.R, true, false, false); - Assert.Equal (expectedCki, AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); } [Fact] @@ -1208,69 +1207,69 @@ public class AnsiEscapeSequenceRequestUtilsTests { ConsoleModifiers mod = 0; char keyChar = '\0'; - Assert.Equal (ConsoleKey.UpArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('A', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.DownArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('B', "", ref mod, ref keyChar)); - Assert.Equal (_key = ConsoleKey.RightArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('C', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.LeftArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('D', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.End, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('F', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Home, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('H', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F1, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('P', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F2, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('Q', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F3, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('R', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F4, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('S', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Tab, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('Z', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('A', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('B', "", ref mod, ref keyChar)); + Assert.Equal (_key = ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('C', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('D', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('F', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('H', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F1, EscSeqUtils.GetConsoleKey ('P', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F2, EscSeqUtils.GetConsoleKey ('Q', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F3, EscSeqUtils.GetConsoleKey ('R', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F4, EscSeqUtils.GetConsoleKey ('S', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Tab, EscSeqUtils.GetConsoleKey ('Z', "", ref mod, ref keyChar)); Assert.Equal (ConsoleModifiers.Shift, mod); - Assert.Equal (0, (int)AnsiEscapeSequenceRequestUtils.GetConsoleKey ('\0', "", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Insert, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "2", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Delete, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "3", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageUp, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "5", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageDown, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "6", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F5, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "15", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F6, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "17", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F7, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "18", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F8, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "19", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F9, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "20", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F10, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "21", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F11, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "23", ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.F12, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "24", ref mod, ref keyChar)); - Assert.Equal (0, (int)AnsiEscapeSequenceRequestUtils.GetConsoleKey ('~', "", ref mod, ref keyChar)); + Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('\0', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('~', "2", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Delete, EscSeqUtils.GetConsoleKey ('~', "3", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('~', "5", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('~', "6", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F5, EscSeqUtils.GetConsoleKey ('~', "15", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F6, EscSeqUtils.GetConsoleKey ('~', "17", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F7, EscSeqUtils.GetConsoleKey ('~', "18", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F8, EscSeqUtils.GetConsoleKey ('~', "19", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F9, EscSeqUtils.GetConsoleKey ('~', "20", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F10, EscSeqUtils.GetConsoleKey ('~', "21", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F11, EscSeqUtils.GetConsoleKey ('~', "23", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F12, EscSeqUtils.GetConsoleKey ('~', "24", ref mod, ref keyChar)); + Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('~', "", ref mod, ref keyChar)); // These terminators are used by macOS on a numeric keypad without keys modifiers - Assert.Equal (ConsoleKey.Add, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('l', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Subtract, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('m', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Insert, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('p', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.End, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('q', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.DownArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('r', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageDown, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('s', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.LeftArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('t', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Clear, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('u', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.RightArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('v', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.Home, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('w', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.UpArrow, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('x', null, ref mod, ref keyChar)); - Assert.Equal (ConsoleKey.PageUp, AnsiEscapeSequenceRequestUtils.GetConsoleKey ('y', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Add, EscSeqUtils.GetConsoleKey ('l', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Subtract, EscSeqUtils.GetConsoleKey ('m', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('p', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('q', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('r', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('s', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('t', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Clear, EscSeqUtils.GetConsoleKey ('u', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('v', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('w', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('x', null, ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('y', null, ref mod, ref keyChar)); } [Fact] public void GetConsoleModifiers_Tests () { - Assert.Equal (ConsoleModifiers.Shift, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("2")); - Assert.Equal (ConsoleModifiers.Alt, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("3")); - Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("4")); - Assert.Equal (ConsoleModifiers.Control, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("5")); - Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("6")); - Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("7")); + Assert.Equal (ConsoleModifiers.Shift, EscSeqUtils.GetConsoleModifiers ("2")); + Assert.Equal (ConsoleModifiers.Alt, EscSeqUtils.GetConsoleModifiers ("3")); + Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Alt, EscSeqUtils.GetConsoleModifiers ("4")); + Assert.Equal (ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("5")); + Assert.Equal (ConsoleModifiers.Shift | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("6")); + Assert.Equal (ConsoleModifiers.Alt | ConsoleModifiers.Control, EscSeqUtils.GetConsoleModifiers ("7")); Assert.Equal ( ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, - AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("8") + EscSeqUtils.GetConsoleModifiers ("8") ); - Assert.Equal (0, (int)AnsiEscapeSequenceRequestUtils.GetConsoleModifiers ("")); + Assert.Equal (0, (int)EscSeqUtils.GetConsoleModifiers ("")); } [Fact] public void GetEscapeResult_Multiple_Tests () { char [] kChars = ['\u001b', '[', '5', ';', '1', '0', 'r']; - (_c1Control, _code, _values, _terminating) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (kChars); + (_c1Control, _code, _values, _terminating) = EscSeqUtils.GetEscapeResult (kChars); Assert.Equal ("CSI", _c1Control); Assert.Null (_code); Assert.Equal (2, _values.Length); @@ -1288,7 +1287,7 @@ public class AnsiEscapeSequenceRequestUtilsTests [InlineData (['A'])] public void GetEscapeResult_Single_Tests (params char [] kChars) { - (_c1Control, _code, _values, _terminating) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (kChars); + (_c1Control, _code, _values, _terminating) = EscSeqUtils.GetEscapeResult (kChars); if (kChars [0] == '\u001B') { @@ -1318,7 +1317,7 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('r', 0, false, false, false) }; - Assert.Equal (new [] { '\u001b', '[', '5', ';', '1', '0', 'r' }, AnsiEscapeSequenceRequestUtils.GetKeyCharArray (cki)); + Assert.Equal (new [] { '\u001b', '[', '5', ';', '1', '0', 'r' }, EscSeqUtils.GetKeyCharArray (cki)); } [Fact] @@ -1337,7 +1336,7 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('3', 0, false, false, false), new ('M', 0, false, false, false) }; - AnsiEscapeSequenceRequestUtils.GetMouse (cki, out List mouseFlags, out Point pos, ProcessContinuousButtonPressed); + EscSeqUtils.GetMouse (cki, out List mouseFlags, out Point pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1Pressed }, mouseFlags); Assert.Equal (new (1, 2), pos); @@ -1353,7 +1352,7 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('3', 0, false, false, false), new ('m', 0, false, false, false) }; - AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (2, mouseFlags.Count); Assert.Equal ( @@ -1374,7 +1373,7 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('3', 0, false, false, false), new ('M', 0, false, false, false) }; - AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1DoubleClicked }, mouseFlags); Assert.Equal (new (1, 2), pos); @@ -1390,7 +1389,7 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('3', 0, false, false, false), new ('M', 0, false, false, false) }; - AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1TripleClicked }, mouseFlags); Assert.Equal (new (1, 2), pos); @@ -1406,7 +1405,7 @@ public class AnsiEscapeSequenceRequestUtilsTests new ('3', 0, false, false, false), new ('m', 0, false, false, false) }; - AnsiEscapeSequenceRequestUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); + EscSeqUtils.GetMouse (cki, out mouseFlags, out pos, ProcessContinuousButtonPressed); Assert.Equal (new () { MouseFlags.Button1Released }, mouseFlags); Assert.Equal (new (1, 2), pos); } @@ -1416,7 +1415,7 @@ public class AnsiEscapeSequenceRequestUtilsTests { ConsoleKeyInfo [] expectedCkInfos = null; var cki = new ConsoleKeyInfo ('\u001b', ConsoleKey.Escape, false, false, false); - expectedCkInfos = AnsiEscapeSequenceRequestUtils.ResizeArray (cki, expectedCkInfos); + expectedCkInfos = EscSeqUtils.ResizeArray (cki, expectedCkInfos); Assert.Single (expectedCkInfos); Assert.Equal (cki, expectedCkInfos [0]); } @@ -1475,7 +1474,7 @@ public class AnsiEscapeSequenceRequestUtilsTests [InlineData ("\r[<35;50;1m[<35;49;1m[<35;47;1m[<35;46;1m[<35;45;2m[<35;44;2m[<35;43;3m[<35;42;3m[<35;41;4m[<35;40;5m[<35;39;6m[<35;49;1m[<35;48;2m[<0;33;6M[<0;33;6mOC_", "_")] public void SplitEscapeRawString_Multiple_Tests (string rawData, string expectedLast) { - List splitList = AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (rawData); + List splitList = EscSeqUtils.SplitEscapeRawString (rawData); Assert.Equal (18, splitList.Count); Assert.Equal ("\r", splitList [0]); Assert.Equal ("\u001b[<35;50;1m", splitList [1]); @@ -1506,7 +1505,7 @@ public class AnsiEscapeSequenceRequestUtilsTests [InlineData ("A")] public void SplitEscapeRawString_Single_Tests (string rawData) { - List splitList = AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (rawData); + List splitList = EscSeqUtils.SplitEscapeRawString (rawData); Assert.Single (splitList); Assert.Equal (rawData, splitList [0]); } @@ -1522,17 +1521,17 @@ public class AnsiEscapeSequenceRequestUtilsTests [InlineData ("0;20t", "\u001b[8;1", 3, "\u001b[80;20t;1")] public void InsertArray_Tests (string toInsert, string current, int? index, string expected) { - ConsoleKeyInfo [] toIns = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (toInsert); - ConsoleKeyInfo [] cki = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (current); - ConsoleKeyInfo [] result = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (expected); + ConsoleKeyInfo [] toIns = EscSeqUtils.ToConsoleKeyInfoArray (toInsert); + ConsoleKeyInfo [] cki = EscSeqUtils.ToConsoleKeyInfoArray (current); + ConsoleKeyInfo [] result = EscSeqUtils.ToConsoleKeyInfoArray (expected); if (index is null) { - cki = AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki); + cki = EscSeqUtils.InsertArray (toIns, cki); } else { - cki = AnsiEscapeSequenceRequestUtils.InsertArray (toIns, cki, (int)index); + cki = EscSeqUtils.InsertArray (toIns, cki, (int)index); } Assert.Equal (result, cki); @@ -1540,7 +1539,7 @@ public class AnsiEscapeSequenceRequestUtilsTests private void ClearAll () { - AnsiEscapeSequenceRequests.Clear (); + EscSeqRequests.Clear (); _newConsoleKeyInfo = default (ConsoleKeyInfo); _key = default (ConsoleKey); _cki = default (ConsoleKeyInfo []); @@ -1550,7 +1549,7 @@ public class AnsiEscapeSequenceRequestUtilsTests _terminating = default (string); _values = default (string []); _isKeyMouse = default (bool); - _seqReqStatus = null; + _isResponse = false; _mouseFlags = default (List); _pos = default (Point); _arg1 = default (MouseFlags); diff --git a/UnitTests/View/Adornment/PaddingTests.cs b/UnitTests/View/Adornment/PaddingTests.cs index a4defc997..0242bcc1d 100644 --- a/UnitTests/View/Adornment/PaddingTests.cs +++ b/UnitTests/View/Adornment/PaddingTests.cs @@ -33,7 +33,5 @@ PPP", output ); TestHelpers.AssertDriverAttributesAre ("0", output, null, view.GetNormalColor ()); - - ((FakeDriver)Application.Driver!).End (); } } From 38e05dd6da1eef05ea1450b46c727ceabed492b2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 00:54:43 +0000 Subject: [PATCH 132/151] Fixing unit test. --- UnitTests/Input/EscSeqUtilsTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 88011b6a8..c2dc4808b 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -854,7 +854,7 @@ public class EscSeqUtilsTests Assert.False (_isKeyMouse); Assert.Equal (new () { 0 }, _mouseFlags); Assert.Equal (Point.Empty, _pos); - Assert.NotNull (_isResponse); + Assert.True (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); @@ -1276,6 +1276,8 @@ public class EscSeqUtilsTests Assert.Equal ("5", _values [0]); Assert.Equal ("10", _values [^1]); Assert.Equal ("r", _terminating); + + ClearAll (); } [Theory] @@ -1301,6 +1303,8 @@ public class EscSeqUtilsTests Assert.Null (_code); Assert.Null (_values); Assert.Null (_terminating); + + ClearAll (); } [Fact] From 5c1c00241812df5b3f0a288dc914113253f93487 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 11:44:06 +0000 Subject: [PATCH 133/151] Avoids initial black screen after moving the mouse at first time. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 66a783a6a..d40652dd3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -683,12 +683,9 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests) { Curses.CheckWinChange (); - ClearContents (); - if (Force16Colors) - { - Curses.refresh (); - } + // On Init this call is needed no mater Force16Colors or not + Curses.refresh (); EscSeqUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed; } From 29fe91981f6b04d4d47be0a8830bf6209ccc6296 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 12:20:26 +0000 Subject: [PATCH 134/151] Revert changes. --- Terminal.Gui/Terminal.Gui.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 007e5ee33..8ef9dadbc 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -169,4 +169,4 @@ - \ No newline at end of file + From e1d727511a021f411f8fa8ab57efcf8430c64f98 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 13:14:52 +0000 Subject: [PATCH 135/151] Fix unit test. --- UnitTests/Input/EscSeqUtilsTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index c2dc4808b..cffe0e1a2 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -969,7 +969,7 @@ public class EscSeqUtilsTests out _isResponse, ProcessContinuousButtonPressed ); - Assert.Single (EscSeqRequests.Statuses); + Assert.Empty (EscSeqRequests.Statuses); Assert.Equal (expectedCki, _newConsoleKeyInfo); Assert.Equal (ConsoleKey.None, _key); Assert.Equal (ConsoleModifiers.None, _mod); @@ -999,6 +999,7 @@ public class EscSeqUtilsTests // Add a request to avoid assert failure in the DecodeEscSeq method EscSeqRequests.Add ("t"); + Assert.Single (EscSeqRequests.Statuses); EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, From be9cb6bbbb4fbf6a10b7ca7f4a6ef6972dde7ec6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 13:33:01 +0000 Subject: [PATCH 136/151] Always reset IncompleteCkInfos. --- Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 6 ++++++ UnitTests/Input/EscSeqUtilsTests.cs | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 7a8a67e36..c18183709 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -331,6 +331,12 @@ public static class EscSeqUtils break; case "CSI": + // Reset always IncompleteCkInfos + if (IncompleteCkInfos is { }) + { + IncompleteCkInfos = null; + } + if (!string.IsNullOrEmpty (code) && code == "<") { GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler); diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index cffe0e1a2..4f783115f 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -954,6 +954,8 @@ public class EscSeqUtilsTests ConsoleKeyInfo expectedCki = default; + Assert.Null (EscSeqUtils.IncompleteCkInfos); + EscSeqUtils.DecodeEscSeq ( ref _newConsoleKeyInfo, ref _key, @@ -983,7 +985,9 @@ public class EscSeqUtilsTests Assert.False (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); + Assert.NotNull (EscSeqUtils.IncompleteCkInfos); Assert.Equal (_cki, EscSeqUtils.IncompleteCkInfos); + Assert.Contains (EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos), EscSeqUtils.ToString (_cki)); _cki = EscSeqUtils.InsertArray ( EscSeqUtils.IncompleteCkInfos, @@ -1032,8 +1036,8 @@ public class EscSeqUtilsTests Assert.True (_isResponse); Assert.Equal (0, (int)_arg1); Assert.Equal (Point.Empty, _arg2); + Assert.Null (EscSeqUtils.IncompleteCkInfos); Assert.NotEqual (_cki, EscSeqUtils.IncompleteCkInfos); - Assert.Contains (EscSeqUtils.ToString (EscSeqUtils.IncompleteCkInfos), EscSeqUtils.ToString (_cki)); ClearAll (); } From 180b06f947ed3e0446c8b3da3b1bf1fad0d074f9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 16:20:03 +0000 Subject: [PATCH 137/151] Simplify and correctness ANSI escape sequences. --- .../EscSeqUtils/EscSeqReqStatus.cs | 4 +- .../EscSeqUtils/EscSeqRequests.cs | 37 ++--- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 2 +- UnitTests/Input/EscSeqRequestsTests.cs | 134 ++++++++++++++++-- 4 files changed, 142 insertions(+), 35 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs index 6674de715..d832f0659 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs @@ -18,10 +18,10 @@ public class EscSeqReqStatus } /// Gets the number of unfinished requests. - public int NumOutstanding { get; set; } + public int NumOutstanding { get; internal set; } /// Gets the number of requests. - public int NumRequests { get; } + public int NumRequests { get; internal set; } /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). public string Terminator { get; } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs index 25925adeb..5ae7dc473 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs @@ -1,7 +1,5 @@ #nullable enable -using System.Collections.Concurrent; - namespace Terminal.Gui; /// @@ -12,27 +10,32 @@ namespace Terminal.Gui; public static class EscSeqRequests { /// Gets the list. - public static List Statuses { get; } = new (); + public static List Statuses { get; } = []; /// /// Adds a new request for the ANSI Escape Sequence defined by . Adds a /// instance to list. /// /// The terminator. - /// The number of requests. - public static void Add (string terminator, int numReq = 1) + /// The number of requests. + public static void Add (string terminator, int numRequests = 1) { + ArgumentException.ThrowIfNullOrEmpty (terminator); + + int numReq = Math.Max (numRequests, 1); + lock (Statuses) { EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator); if (found is null) { - Statuses.Add (new EscSeqReqStatus (terminator, numReq)); + Statuses.Add (new (terminator, numReq)); } - else if (found.NumOutstanding < found.NumRequests) + else { - found.NumOutstanding = Math.Min (found.NumOutstanding + numReq, found.NumRequests); + found.NumRequests += numReq; + found.NumOutstanding += numReq; } } } @@ -60,21 +63,7 @@ public static class EscSeqRequests { EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator); - if (found is null) - { - return false; - } - - if (found is { NumOutstanding: > 0 }) - { - return true; - } - - // BUGBUG: Why does an API that returns a bool remove the entry from the list? - // NetDriver and Unit tests never exercise this line of code. Maybe Curses does? - Statuses.Remove (found); - - return false; + return found is { }; } } @@ -87,6 +76,8 @@ public static class EscSeqRequests /// The terminating string. public static void Remove (string terminator) { + ArgumentException.ThrowIfNullOrEmpty (terminator); + lock (Statuses) { EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator); diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index c18183709..709f97f5f 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -204,7 +204,7 @@ public static class EscSeqUtils out List buttonState, out Point pos, out bool isResponse, - Action continuousButtonPressedHandler + Action? continuousButtonPressedHandler ) { char [] kChars = GetKeyCharArray (cki); diff --git a/UnitTests/Input/EscSeqRequestsTests.cs b/UnitTests/Input/EscSeqRequestsTests.cs index fd6e0510b..a39dae1b1 100644 --- a/UnitTests/Input/EscSeqRequestsTests.cs +++ b/UnitTests/Input/EscSeqRequestsTests.cs @@ -5,7 +5,6 @@ public class EscSeqRequestsTests [Fact] public void Add_Tests () { - EscSeqRequests.Clear (); EscSeqRequests.Add ("t"); Assert.Single (EscSeqRequests.Statuses); Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); @@ -15,8 +14,8 @@ public class EscSeqRequestsTests EscSeqRequests.Add ("t", 2); Assert.Single (EscSeqRequests.Statuses); Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); - Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests); - Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding); + Assert.Equal (3, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (3, EscSeqRequests.Statuses [^1].NumOutstanding); EscSeqRequests.Clear (); EscSeqRequests.Add ("t", 2); @@ -28,14 +27,15 @@ public class EscSeqRequestsTests EscSeqRequests.Add ("t", 3); Assert.Single (EscSeqRequests.Statuses); Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); - Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests); - Assert.Equal (2, EscSeqRequests.Statuses [^1].NumOutstanding); + Assert.Equal (5, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (5, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqRequests.Clear (); } [Fact] public void Constructor_Defaults () { - EscSeqRequests.Clear (); Assert.NotNull (EscSeqRequests.Statuses); Assert.Empty (EscSeqRequests.Statuses); } @@ -43,7 +43,6 @@ public class EscSeqRequestsTests [Fact] public void Remove_Tests () { - EscSeqRequests.Clear (); EscSeqRequests.Add ("t"); EscSeqRequests.Remove ("t"); Assert.Empty (EscSeqRequests.Statuses); @@ -57,16 +56,133 @@ public class EscSeqRequestsTests EscSeqRequests.Remove ("t"); Assert.Empty (EscSeqRequests.Statuses); + + EscSeqRequests.Clear (); } [Fact] - public void Requested_Tests () + public void HasResponse_Tests () { - EscSeqRequests.Clear (); Assert.False (EscSeqRequests.HasResponse ("t")); EscSeqRequests.Add ("t"); Assert.False (EscSeqRequests.HasResponse ("r")); Assert.True (EscSeqRequests.HasResponse ("t")); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqRequests.Remove ("t"); + Assert.Empty (EscSeqRequests.Statuses); + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + public void Add_Null_Or_Empty_Terminator_Throws (string terminator) + { + if (terminator is null) + { + Assert.Throws (() => EscSeqRequests.Add (terminator)); + } + else + { + Assert.Throws (() => EscSeqRequests.Add (terminator)); + } + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + public void HasResponse_Null_Or_Empty_Terminator_Does_Not_Throws (string terminator) + { + EscSeqRequests.Add ("t"); + + Assert.False (EscSeqRequests.HasResponse (terminator)); + + EscSeqRequests.Clear (); + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + public void Remove_Null_Or_Empty_Terminator_Throws (string terminator) + { + EscSeqRequests.Add ("t"); + + if (terminator is null) + { + Assert.Throws (() => EscSeqRequests.Remove (terminator)); + } + else + { + Assert.Throws (() => EscSeqRequests.Remove (terminator)); + } + + EscSeqRequests.Clear (); + } + + [Fact] + public void Requests_Responses_Tests () + { + // This is simulated response from a CSI_ReportTerminalSizeInChars + ConsoleKeyInfo [] cki = + [ + new ('\u001b', 0, false, false, false), + new ('[', 0, false, false, false), + new ('8', 0, false, false, false), + new (';', 0, false, false, false), + new ('1', 0, false, false, false), + new ('0', 0, false, false, false), + new (';', 0, false, false, false), + new ('2', 0, false, false, false), + new ('0', 0, false, false, false), + new ('t', 0, false, false, false) + ]; + ConsoleKeyInfo newConsoleKeyInfo = default; + ConsoleKey key = default; + ConsoleModifiers mod = default; + + Assert.Empty (EscSeqRequests.Statuses); + + EscSeqRequests.Add ("t"); + Assert.Single (EscSeqRequests.Statuses); + Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests); + Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding); + + EscSeqUtils.DecodeEscSeq ( + ref newConsoleKeyInfo, + ref key, + cki, + ref mod, + out string c1Control, + out string code, + out string [] values, + out string terminating, + out bool isKeyMouse, + out List mouseFlags, + out Point pos, + out bool isResponse, + null + ); + + Assert.Empty (EscSeqRequests.Statuses); + Assert.Equal (default, newConsoleKeyInfo); + Assert.Equal (default, key); + Assert.Equal (10, cki.Length); + Assert.Equal (default, mod); + Assert.Equal ("CSI", c1Control); + Assert.Null (code); + // ReSharper disable once HeuristicUnreachableCode + Assert.Equal (3, values.Length); + Assert.Equal ("8", values [0]); + Assert.Equal ("t", terminating); + Assert.False (isKeyMouse); + Assert.Single (mouseFlags); + Assert.Equal (default, mouseFlags [^1]); + Assert.Equal (Point.Empty, pos); + Assert.True (isResponse); } } From 0446c22335b2520b89af10f294757424b743e4d6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 16:33:01 +0000 Subject: [PATCH 138/151] NumOutstanding is never equal to zero because they are always removed. --- .../ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs index 5ae7dc473..579aa0cd9 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs @@ -87,19 +87,12 @@ public static class EscSeqRequests return; } + found.NumOutstanding--; + if (found.NumOutstanding == 0) { Statuses.Remove (found); } - else if (found.NumOutstanding > 0) - { - found.NumOutstanding--; - - if (found.NumOutstanding == 0) - { - Statuses.Remove (found); - } - } } } } From e9e12ab140b6b46d8adae2f71b5a3db7e0e2c45e Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 17:31:46 +0000 Subject: [PATCH 139/151] Trying to fix unit tests. --- UnitTests/TestHelpers.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 85bbc7d1d..19a532ad4 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -199,6 +199,8 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute if (Application.Driver is { }) { + ((FakeDriver)Application.Driver).Rows = 25; + ((FakeDriver)Application.Driver).Cols = 25; ((FakeDriver)Application.Driver).End (); } From 0b26355c4c862869e8a83d6bc77a32f2969736d1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 23 Nov 2024 18:30:10 +0000 Subject: [PATCH 140/151] Fix unit test failing. --- UnitTests/Application/SynchronizatonContextTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UnitTests/Application/SynchronizatonContextTests.cs b/UnitTests/Application/SynchronizatonContextTests.cs index 9f9804649..9f3c507d4 100644 --- a/UnitTests/Application/SynchronizatonContextTests.cs +++ b/UnitTests/Application/SynchronizatonContextTests.cs @@ -7,6 +7,7 @@ public class SyncrhonizationContextTests [Fact] public void SynchronizationContext_CreateCopy () { + ConsoleDriver.RunningUnitTests = true; Application.Init (); SynchronizationContext context = SynchronizationContext.Current; Assert.NotNull (context); @@ -61,6 +62,7 @@ public class SyncrhonizationContextTests [AutoInitShutdown] public void SynchronizationContext_Send () { + ConsoleDriver.RunningUnitTests = true; Application.Init (); SynchronizationContext context = SynchronizationContext.Current; From 90d492716a2b8923ab34b716d36f7b938fd08545 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 24 Nov 2024 11:01:51 +0000 Subject: [PATCH 141/151] Adds IConsoleDriver --- NativeAot/Program.cs | 4 +- SelfContained/Program.cs | 2 +- .../Application/Application.Driver.cs | 8 +- .../Application/Application.Initialization.cs | 20 +- .../Application/Application.Keyboard.cs | 4 +- Terminal.Gui/Application/Application.Run.cs | 8 +- .../Application/Application.Screen.cs | 4 +- Terminal.Gui/Application/Application.cs | 6 +- Terminal.Gui/Application/MainLoop.cs | 4 +- Terminal.Gui/Clipboard/Clipboard.cs | 2 +- Terminal.Gui/Clipboard/ClipboardBase.cs | 4 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 339 ++++++++++++++++-- .../CursesDriver/CursesDriver.cs | 10 +- .../CursesDriver/UnixMainLoop.cs | 6 +- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 6 +- .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 2 +- Terminal.Gui/ConsoleDrivers/KeyCode.cs | 2 +- .../ConsoleDrivers/NetDriver/NetDriver.cs | 6 +- .../ConsoleDrivers/NetDriver/NetEvents.cs | 6 +- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 10 +- .../WindowsDriver/WindowsDriver.cs | 14 +- .../WindowsDriver/WindowsMainLoop.cs | 8 +- Terminal.Gui/Drawing/Attribute.cs | 2 +- Terminal.Gui/Drawing/Cell.cs | 2 +- Terminal.Gui/Drawing/LineCanvas.cs | 6 +- Terminal.Gui/Drawing/SixelToRender.cs | 2 +- Terminal.Gui/README.md | 4 +- Terminal.Gui/Text/TextFormatter.cs | 4 +- Terminal.Gui/View/View.cs | 2 +- Terminal.Gui/Views/ColorPicker.Prompt.cs | 2 +- Terminal.Gui/Views/GraphView/Axis.cs | 2 +- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- Terminal.Gui/Views/TableView/TableStyle.cs | 2 +- Terminal.Gui/Views/TableView/TableView.cs | 4 +- Terminal.Gui/Views/TreeView/Branch.cs | 10 +- UICatalog/Scenarios/GraphViewExample.cs | 2 +- UICatalog/UICatalog.cs | 2 +- UnitTests/Application/ApplicationTests.cs | 4 +- UnitTests/ConsoleDrivers/AddRuneTests.cs | 2 +- UnitTests/ConsoleDrivers/ClipRegionTests.cs | 6 +- .../ConsoleDrivers/ConsoleDriverTests.cs | 12 +- UnitTests/ConsoleDrivers/ContentsTests.cs | 6 +- UnitTests/ConsoleDrivers/DriverColorTests.cs | 6 +- .../ConsoleDrivers/MainLoopDriverTests.cs | 24 +- UnitTests/TestHelpers.cs | 24 +- 45 files changed, 447 insertions(+), 160 deletions(-) diff --git a/NativeAot/Program.cs b/NativeAot/Program.cs index bb0f38bc0..e9c8cf77f 100644 --- a/NativeAot/Program.cs +++ b/NativeAot/Program.cs @@ -9,8 +9,8 @@ namespace NativeAot; public static class Program { - [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")] - [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")] + [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")] + [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")] private static void Main (string [] args) { Application.Init (); diff --git a/SelfContained/Program.cs b/SelfContained/Program.cs index 29b7f5cd9..c57f39e58 100644 --- a/SelfContained/Program.cs +++ b/SelfContained/Program.cs @@ -9,7 +9,7 @@ namespace SelfContained; public static class Program { - [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run(Func, ConsoleDriver)")] + [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run(Func, IConsoleDriver)")] private static void Main (string [] args) { Application.Init (); diff --git a/Terminal.Gui/Application/Application.Driver.cs b/Terminal.Gui/Application/Application.Driver.cs index c2f6431db..7b3340cbd 100644 --- a/Terminal.Gui/Application/Application.Driver.cs +++ b/Terminal.Gui/Application/Application.Driver.cs @@ -5,13 +5,13 @@ public static partial class Application // Driver abstractions { internal static bool _forceFakeConsole; - /// Gets the that has been selected. See also . - public static ConsoleDriver? Driver { get; internal set; } + /// Gets the that has been selected. See also . + public static IConsoleDriver? Driver { get; internal set; } /// /// Gets or sets whether will be forced to output only the 16 colors defined in /// . The default is , meaning 24-bit (TrueColor) colors will be output - /// as long as the selected supports TrueColor. + /// as long as the selected supports TrueColor. /// [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] public static bool Force16Colors { get; set; } @@ -21,7 +21,7 @@ public static partial class Application // Driver abstractions /// specified, the driver is selected based on the platform. /// /// - /// Note, will override this configuration setting if called + /// Note, will override this configuration setting if called /// with either `driver` or `driverName` specified. /// [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 47c07a19f..31cfa09df 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -10,7 +10,7 @@ public static partial class Application // Initialization (Init/Shutdown) /// Initializes a new instance of Application. /// Call this method once per instance (or after has been called). /// - /// This function loads the right for the platform, Creates a . and + /// This function loads the right for the platform, Creates a . and /// assigns it to /// /// @@ -21,23 +21,23 @@ public static partial class Application // Initialization (Init/Shutdown) /// /// /// The function combines - /// and + /// and /// into a single /// call. An application cam use without explicitly calling - /// . + /// . /// /// - /// The to use. If neither or + /// The to use. If neither or /// are specified the default driver for the platform will be used. /// /// /// The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the - /// to use. If neither or are + /// to use. If neither or are /// specified the default driver for the platform will be used. /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static void Init (ConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); } + public static void Init (IConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); } internal static int MainThreadId { get; set; } = -1; @@ -53,7 +53,7 @@ public static partial class Application // Initialization (Init/Shutdown) [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] internal static void InternalInit ( - ConsoleDriver? driver = null, + IConsoleDriver? driver = null, string? driverName = null, bool calledViaRunT = false ) @@ -136,7 +136,7 @@ public static partial class Application // Initialization (Init/Shutdown) if (driverType is { }) { - Driver = (ConsoleDriver)Activator.CreateInstance (driverType)!; + Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!; } else { @@ -181,7 +181,7 @@ public static partial class Application // Initialization (Init/Shutdown) private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); } private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); } - /// Gets of list of types that are available. + /// Gets of list of types that are available. /// [RequiresUnreferencedCode ("AOT")] public static List GetDriverTypes () @@ -193,7 +193,7 @@ public static partial class Application // Initialization (Init/Shutdown) { foreach (Type? type in asm.GetTypes ()) { - if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) + if (type.IsSubclassOf (typeof (IConsoleDriver)) && !type.IsAbstract) { driverTypes.Add (type); } diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 18881d6c5..16ddcf66c 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui; public static partial class Application // Keyboard handling { /// - /// Called when the user presses a key (by the ). Raises the cancelable + /// Called when the user presses a key (by the ). Raises the cancelable /// event, then calls on all top level views, and finally /// if the key was not handled, invokes any Application-scoped . /// @@ -111,7 +111,7 @@ public static partial class Application // Keyboard handling public static event EventHandler? KeyDown; /// - /// Called when the user releases a key (by the ). Raises the cancelable + /// Called when the user releases a key (by the ). Raises the cancelable /// event /// then calls on all top level views. Called after . /// diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 7511f8216..cf70ff5e3 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -305,7 +305,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// The created object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static Toplevel Run (Func? errorHandler = null, ConsoleDriver? driver = null) { return Run (errorHandler, driver); } + public static Toplevel Run (Func? errorHandler = null, IConsoleDriver? driver = null) { return Run (errorHandler, driver); } /// /// Runs the application by creating a -derived object of type T and calling @@ -323,14 +323,14 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// /// /// - /// The to use. If not specified the default driver for the platform will + /// The to use. If not specified the default driver for the platform will /// be used ( , , or ). Must be /// if has already been called. /// /// The created T object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static T Run (Func? errorHandler = null, ConsoleDriver? driver = null) + public static T Run (Func? errorHandler = null, IConsoleDriver? driver = null) where T : Toplevel, new() { if (!Initialized) @@ -369,7 +369,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// return control immediately. /// /// When using or - /// + /// /// will be called automatically. /// /// diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs index c5bf6d6fd..a322aa31f 100644 --- a/Terminal.Gui/Application/Application.Screen.cs +++ b/Terminal.Gui/Application/Application.Screen.cs @@ -6,11 +6,11 @@ public static partial class Application // Screen related stuff private static Rectangle? _screen; /// - /// Gets or sets the size of the screen. By default, this is the size of the screen as reported by the . + /// Gets or sets the size of the screen. By default, this is the size of the screen as reported by the . /// /// /// - /// If the has not been initialized, this will return a default size of 2048x2048; useful for unit tests. + /// If the has not been initialized, this will return a default size of 2048x2048; useful for unit tests. /// /// public static Rectangle Screen diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index d9e6c68d9..216f8a90d 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -32,7 +32,7 @@ public static partial class Application /// A string representation of the Application public new static string ToString () { - ConsoleDriver? driver = Driver; + IConsoleDriver? driver = Driver; if (driver is null) { @@ -43,11 +43,11 @@ public static partial class Application } /// - /// Gets a string representation of the Application rendered by the provided . + /// Gets a string representation of the Application rendered by the provided . /// /// The driver to use to render the contents. /// A string representation of the Application - public static string ToString (ConsoleDriver? driver) + public static string ToString (IConsoleDriver? driver) { if (driver is null) { diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs index 9d8c08d37..2d463ab70 100644 --- a/Terminal.Gui/Application/MainLoop.cs +++ b/Terminal.Gui/Application/MainLoop.cs @@ -37,7 +37,7 @@ internal interface IMainLoopDriver /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. /// -internal class MainLoop : IDisposable +public class MainLoop : IDisposable { internal List> _idleHandlers = new (); internal SortedList _timeouts = new (); @@ -50,7 +50,7 @@ internal class MainLoop : IDisposable /// Creates a new MainLoop. /// Use to release resources. /// - /// The instance (one of the implementations FakeMainLoop, UnixMainLoop, + /// The instance (one of the implementations FakeMainLoop, UnixMainLoop, /// NetMainLoop or WindowsMainLoop). /// internal MainLoop (IMainLoopDriver driver) diff --git a/Terminal.Gui/Clipboard/Clipboard.cs b/Terminal.Gui/Clipboard/Clipboard.cs index f8bf907c7..ecb59205f 100644 --- a/Terminal.Gui/Clipboard/Clipboard.cs +++ b/Terminal.Gui/Clipboard/Clipboard.cs @@ -111,7 +111,7 @@ public static class Clipboard /// /// Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by -/// CursesDriver, but also used in Unit tests which is why it is in ConsoleDriver.cs. +/// CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs. /// internal static class ClipboardProcessRunner { diff --git a/Terminal.Gui/Clipboard/ClipboardBase.cs b/Terminal.Gui/Clipboard/ClipboardBase.cs index 8c01e9c43..8c1c0a93a 100644 --- a/Terminal.Gui/Clipboard/ClipboardBase.cs +++ b/Terminal.Gui/Clipboard/ClipboardBase.cs @@ -103,7 +103,7 @@ public abstract class ClipboardBase : IClipboard } /// - /// Returns the contents of the OS clipboard if possible. Implemented by -specific + /// Returns the contents of the OS clipboard if possible. Implemented by -specific /// subclasses. /// /// The contents of the OS clipboard if successful. @@ -111,7 +111,7 @@ public abstract class ClipboardBase : IClipboard protected abstract string GetClipboardDataImpl (); /// - /// Pastes the to the OS clipboard if possible. Implemented by + /// Pastes the to the OS clipboard if possible. Implemented by /// -specific subclasses. /// /// The text to paste to the OS clipboard. diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 9b57f20db..c4e0e385c 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -4,13 +4,300 @@ using System.Diagnostics; namespace Terminal.Gui; +public interface IConsoleDriver +{ + /// Get the operating system clipboard. + IClipboard? Clipboard { get; } + + /// Gets the location and size of the terminal screen. + Rectangle Screen { get; } + + /// + /// Gets or sets the clip rectangle that and are subject + /// to. + /// + /// The rectangle describing the of region. + Region? Clip { get; set; } + + /// + /// Gets the column last set by . and are used by + /// and to determine where to add content. + /// + int Col { get; } + + /// The number of columns visible in the terminal. + int Cols { get; set; } + + /// + /// The contents of the application output. The driver outputs this buffer to the terminal when + /// is called. + /// The format of the array is rows, columns. The first index is the row, the second index is the column. + /// + Cell [,]? Contents { get; set; } + + /// The leftmost column in the terminal. + int Left { get; set; } + + /// + /// Gets the row last set by . and are used by + /// and to determine where to add content. + /// + int Row { get; } + + /// The number of rows visible in the terminal. + int Rows { get; set; } + + /// The topmost row in the terminal. + int Top { get; set; } + + /// Gets whether the supports TrueColor output. + bool SupportsTrueColor { get; } + + /// + /// 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. + /// + /// + bool Force16Colors { get; set; } + + /// + /// The that will be used for the next or + /// call. + /// + Attribute CurrentAttribute { get; set; } + + /// Returns the name of the driver and relevant library version information. + /// + string GetVersionInfo (); + + /// + /// Provide proper writing to send escape sequence recognized by the . + /// + /// + void WriteRaw (string ansi); + + /// 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. + /// + bool IsRuneSupported (Rune rune); + + /// Tests whether the specified coordinate are valid for drawing. + /// The column. + /// The row. + /// + /// if the coordinate is outside the screen bounds or outside of . + /// otherwise. + /// + bool IsValidLocation (int col, int row); + + /// 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. + /// + bool IsValidLocation (Rune rune, int col, int 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. + void Move (int col, int row); + + /// 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. + void AddRune (Rune 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. + void AddRune (char 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. + void AddStr (string str); + + /// 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 + void FillRect (Rectangle rect, Rune rune = default); + + /// + /// Fills the specified rectangle with the specified . This method is a convenience method + /// that calls . + /// + /// + /// + void FillRect (Rectangle rect, char c); + + /// Clears the of the driver. + void ClearContents (); + + /// + /// Raised each time is called. For benchmarking. + /// + event EventHandler? ClearedContents; + + /// + /// Sets as dirty for situations where views + /// don't need layout and redrawing, but just refresh the screen. + /// + void SetContentsAsDirty (); + + /// Determines if the terminal cursor should be visible or not and sets it accordingly. + /// upon success + bool EnsureCursorVisibility (); + + /// Gets the terminal cursor visibility. + /// The current + /// upon success + bool GetCursorVisibility (out CursorVisibility visibility); + + /// Called when the terminal size changes. Fires the event. + /// + void OnSizeChanged (SizeChangedEventArgs args); + + /// Updates the screen to reflect all the changes that have been done to the display buffer + void Refresh (); + + /// + /// Raised each time is called. For benchmarking. + /// + event EventHandler>? Refreshed; + + /// Sets the terminal cursor visibility. + /// The wished + /// upon success + bool SetCursorVisibility (CursorVisibility visibility); + + /// The event fired when the terminal is resized. + event EventHandler? SizeChanged; + + /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. + /// This is only implemented in . + void Suspend (); + + /// Sets the position of the terminal cursor to and . + void UpdateCursor (); + + /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. + /// if any updates to the screen were made. + bool UpdateScreen (); + + /// Initializes the driver + /// Returns an instance of using the for the driver. + MainLoop Init (); + + /// Ends the execution of the console driver. + void End (); + + /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. + /// Implementations should call base.SetAttribute(c). + /// C. + Attribute SetAttribute (Attribute c); + + /// Gets the current . + /// The current attribute. + Attribute GetAttribute (); + + /// Makes an . + /// The foreground color. + /// The background color. + /// The attribute for the foreground and background colors. + Attribute MakeColor (in Color foreground, in Color background); + + /// Event fired when a mouse event occurs. + event EventHandler? MouseEvent; + + /// Called when a mouse event occurs. Fires the event. + /// + void OnMouseEvent (MouseEventArgs a); + + /// Event fired when a key is pressed down. This is a precursor to . + event EventHandler? KeyDown; + + /// + /// Called when a key is pressed down. Fires the event. This is a precursor to + /// . + /// + /// + void OnKeyDown (Key a); + + /// Event fired when a key is released. + /// + /// Drivers that do not support key release events will fire this event after processing is + /// complete. + /// + event EventHandler? KeyUp; + + /// Called when a key is released. Fires the event. + /// + /// Drivers that do not support key release events will call this method after processing + /// is complete. + /// + /// + void OnKeyUp (Key a); + + /// Simulates a key press. + /// The key character. + /// The key. + /// If simulates the Shift key being pressed. + /// If simulates the Alt key being pressed. + /// If simulates the Ctrl key being pressed. + void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); +} + /// Base class for Terminal.Gui ConsoleDriver implementations. /// /// There are currently four implementations: - (for Unix and Mac) - /// - that uses the .NET Console API - /// for unit testing. /// -public abstract class ConsoleDriver +public abstract class ConsoleDriver : IConsoleDriver { /// /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. @@ -38,7 +325,7 @@ public abstract class ConsoleDriver /// Provide proper writing to send escape sequence recognized by the . /// /// - internal abstract void WriteRaw (string ansi); + public abstract void WriteRaw (string ansi); #endregion ANSI Esc Sequence Handling @@ -50,7 +337,7 @@ public abstract class ConsoleDriver // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application? /// Gets the location and size of the terminal screen. - internal Rectangle Screen => new (0, 0, Cols, Rows); + public Rectangle Screen => new (0, 0, Cols, Rows); private Region? _clip; @@ -59,7 +346,7 @@ public abstract class ConsoleDriver /// to. /// /// The rectangle describing the of region. - internal Region? Clip + public Region? Clip { get => _clip; set @@ -83,10 +370,10 @@ public abstract class ConsoleDriver /// Gets the column last set by . and are used by /// and to determine where to add content. /// - internal int Col { get; private set; } + public int Col { get; private set; } /// The number of columns visible in the terminal. - internal virtual int Cols + public virtual int Cols { get => _cols; set @@ -101,10 +388,10 @@ public abstract class ConsoleDriver /// is called. /// The format of the array is rows, columns. The first index is the row, the second index is the column. /// - internal Cell [,]? Contents { get; set; } + public Cell [,]? Contents { get; set; } /// The leftmost column in the terminal. - internal virtual int Left { get; set; } = 0; + public virtual int Left { get; set; } = 0; /// Tests if the specified rune is supported by the driver. /// @@ -147,10 +434,10 @@ public abstract class ConsoleDriver /// Gets the row last set by . and are used by /// and to determine where to add content. /// - internal int Row { get; private set; } + public int Row { get; private set; } /// The number of rows visible in the terminal. - internal virtual int Rows + public virtual int Rows { get => _rows; set @@ -161,7 +448,7 @@ public abstract class ConsoleDriver } /// The topmost row in the terminal. - internal virtual int Top { get; set; } = 0; + public virtual int Top { get; set; } = 0; /// Adds the specified rune to the display at the current cursor position. /// @@ -177,7 +464,7 @@ public abstract class ConsoleDriver /// /// /// Rune to add. - internal void AddRune (Rune rune) + public void AddRune (Rune rune) { int runeWidth = -1; bool validLocation = IsValidLocation (rune, Col, Row); @@ -352,7 +639,7 @@ public abstract class ConsoleDriver /// convenience method that calls with the constructor. /// /// Character to add. - internal void AddRune (char c) { AddRune (new Rune (c)); } + public void AddRune (char c) { AddRune (new Rune (c)); } /// Adds the to the display at the cursor position. /// @@ -364,7 +651,7 @@ public abstract class ConsoleDriver /// If requires more columns than are available, the output will be clipped. /// /// String. - internal void AddStr (string str) + public void AddStr (string str) { List runes = str.EnumerateRunes ().ToList (); @@ -380,7 +667,7 @@ public abstract class ConsoleDriver /// /// The Screen-relative rectangle. /// The Rune used to fill the rectangle - internal void FillRect (Rectangle rect, Rune rune = default) + public void FillRect (Rectangle rect, Rune rune = default) { // BUGBUG: This should be a method on Region rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen); @@ -406,7 +693,7 @@ public abstract class ConsoleDriver } /// Clears the of the driver. - internal void ClearContents () + public void ClearContents () { Contents = new Cell [Rows, Cols]; @@ -446,7 +733,7 @@ public abstract class ConsoleDriver /// Sets as dirty for situations where views /// don't need layout and redrawing, but just refresh the screen. /// - internal void SetContentsAsDirty () + public void SetContentsAsDirty () { lock (Contents!) { @@ -468,7 +755,7 @@ public abstract class ConsoleDriver /// /// /// - internal void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); } + public void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); } #endregion Screen and Contents @@ -491,7 +778,7 @@ public abstract class ConsoleDriver /// if the coordinate is outside the screen bounds or outside of . /// otherwise. /// - internal bool IsValidLocation (Rune rune, int col, int row) + public bool IsValidLocation (Rune rune, int col, int row) { if (rune.GetColumns () < 2) { @@ -506,10 +793,10 @@ public abstract class ConsoleDriver /// Called when the terminal size changes. Fires the event. /// - internal void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); } + public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); } /// Updates the screen to reflect all the changes that have been done to the display buffer - internal void Refresh () + public void Refresh () { bool updated = UpdateScreen (); UpdateCursor (); @@ -547,10 +834,10 @@ public abstract class ConsoleDriver /// Initializes the driver /// Returns an instance of using the for the driver. - internal abstract MainLoop Init (); + public abstract MainLoop Init (); /// Ends the execution of the console driver. - internal abstract void End (); + public abstract void End (); #endregion @@ -571,7 +858,7 @@ public abstract class ConsoleDriver /// , indicating that the cannot support TrueColor. /// /// - internal virtual bool Force16Colors + public virtual bool Force16Colors { get => Application.Force16Colors || !SupportsTrueColor; set => Application.Force16Colors = value || !SupportsTrueColor; @@ -605,7 +892,7 @@ public abstract class ConsoleDriver /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. /// Implementations should call base.SetAttribute(c). /// C. - internal Attribute SetAttribute (Attribute c) + public Attribute SetAttribute (Attribute c) { Attribute prevAttribute = CurrentAttribute; CurrentAttribute = c; @@ -615,7 +902,7 @@ public abstract class ConsoleDriver /// Gets the current . /// The current attribute. - internal Attribute GetAttribute () { return CurrentAttribute; } + public Attribute GetAttribute () { return CurrentAttribute; } // TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be // removed (and Attribute can lose the platformColor property). diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index d40652dd3..43330eab3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -14,7 +14,7 @@ internal class CursesDriver : ConsoleDriver { public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; } - internal override int Cols + public override int Cols { get => Curses.Cols; set @@ -24,7 +24,7 @@ internal class CursesDriver : ConsoleDriver } } - internal override int Rows + public override int Rows { get => Curses.Lines; set @@ -582,7 +582,7 @@ internal class CursesDriver : ConsoleDriver private UnixMainLoop? _mainLoopDriver; private object _processInputToken; - internal override MainLoop Init () + public override MainLoop Init () { _mainLoopDriver = new (this); @@ -1084,7 +1084,7 @@ internal class CursesDriver : ConsoleDriver } } - internal override void End () + public override void End () { EscSeqUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed; @@ -1128,7 +1128,7 @@ internal class CursesDriver : ConsoleDriver } /// - internal override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); } + public override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); } } // TODO: One type per file - move to another file diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index c6414a2b9..28df408ea 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -48,11 +48,11 @@ internal class UnixMainLoop : IMainLoopDriver private Pollfd []? _pollMap; private bool _winChanged; - public UnixMainLoop (ConsoleDriver consoleDriver) + public UnixMainLoop (IConsoleDriver IConsoleDriver) { - ArgumentNullException.ThrowIfNull (consoleDriver); + ArgumentNullException.ThrowIfNull (IConsoleDriver); - _cursesDriver = (CursesDriver)consoleDriver; + _cursesDriver = (CursesDriver)IConsoleDriver; } void IMainLoopDriver.Wakeup () diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 7d82737cc..a9117cee4 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -76,7 +76,7 @@ public class FakeDriver : ConsoleDriver } } - internal override void End () + public override void End () { FakeConsole.ResetColor (); FakeConsole.Clear (); @@ -84,7 +84,7 @@ public class FakeDriver : ConsoleDriver private FakeMainLoop _mainLoopDriver; - internal override MainLoop Init () + public override MainLoop Init () { FakeConsole.MockKeyPresses.Clear (); @@ -393,7 +393,7 @@ public class FakeDriver : ConsoleDriver } /// - internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } + public override void WriteRaw (string ansi) { throw new NotImplementedException (); } public void SetBufferSize (int width, int height) { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index d90caace7..ebc3ae684 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -4,7 +4,7 @@ internal class FakeMainLoop : IMainLoopDriver { public Action MockKeyPressed; - public FakeMainLoop (ConsoleDriver consoleDriver = null) + public FakeMainLoop (IConsoleDriver IConsoleDriver = null) { // No implementation needed for FakeMainLoop } diff --git a/Terminal.Gui/ConsoleDrivers/KeyCode.cs b/Terminal.Gui/ConsoleDrivers/KeyCode.cs index 183322ec8..2a89667ea 100644 --- a/Terminal.Gui/ConsoleDrivers/KeyCode.cs +++ b/Terminal.Gui/ConsoleDrivers/KeyCode.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; /// -/// The enumeration encodes key information from s and provides a +/// The enumeration encodes key information from s and provides a /// consistent way for application code to specify keys and receive key events. /// /// The class provides a higher-level abstraction, with helper methods and properties for diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index c477f27e6..62c6db94b 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -226,7 +226,7 @@ internal class NetDriver : ConsoleDriver internal NetMainLoop? _mainLoopDriver; - internal override MainLoop Init () + public override MainLoop Init () { PlatformID p = Environment.OSVersion.Platform; @@ -350,7 +350,7 @@ internal class NetDriver : ConsoleDriver } } - internal override void End () + public override void End () { if (IsWinPlatform) { @@ -717,7 +717,7 @@ internal class NetDriver : ConsoleDriver #region Low-Level DotNet tuff /// - internal override void WriteRaw (string ansi) + public override void WriteRaw (string ansi) { Console.Out.Write (ansi); Console.Out.Flush (); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index d03a1fd9d..c0dcaffb4 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -8,15 +8,15 @@ internal class NetEvents : IDisposable private readonly ManualResetEventSlim _inputReady = new (false); private CancellationTokenSource? _inputReadyCancellationTokenSource; private readonly Queue _inputQueue = new (); - private readonly ConsoleDriver _consoleDriver; + private readonly IConsoleDriver _consoleDriver; private ConsoleKeyInfo []? _cki; private bool _isEscSeq; #if PROCESS_REQUEST bool _neededProcessRequest; #endif - public NetEvents (ConsoleDriver consoleDriver) + public NetEvents (IConsoleDriver IConsoleDriver) { - _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); + _consoleDriver = IConsoleDriver ?? throw new ArgumentNullException (nameof (IConsoleDriver)); _inputReadyCancellationTokenSource = new (); Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 08efe66d5..7632c49fb 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -24,16 +24,16 @@ internal class NetMainLoop : IMainLoopDriver private MainLoop? _mainLoop; /// Initializes the class with the console driver. - /// Passing a consoleDriver is provided to capture windows resizing. - /// The console driver used by this Net main loop. + /// Passing a IConsoleDriver is provided to capture windows resizing. + /// The console driver used by this Net main loop. /// - public NetMainLoop (ConsoleDriver consoleDriver) + public NetMainLoop (IConsoleDriver IConsoleDriver) { - ArgumentNullException.ThrowIfNull (consoleDriver); + ArgumentNullException.ThrowIfNull (IConsoleDriver); if (!ConsoleDriver.RunningUnitTests) { - _netEvents = new (consoleDriver); + _netEvents = new (IConsoleDriver); } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index f18616fd2..251ad4dad 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -187,7 +187,7 @@ internal class WindowsDriver : ConsoleDriver } } - internal override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } + public override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } #region Not Implemented @@ -402,7 +402,7 @@ internal class WindowsDriver : ConsoleDriver return updated; } - internal override void End () + public override void End () { if (_mainLoopDriver is { }) { @@ -424,7 +424,7 @@ internal class WindowsDriver : ConsoleDriver } } - internal override MainLoop Init () + public override MainLoop Init () { _mainLoopDriver = new WindowsMainLoop (this); @@ -867,7 +867,7 @@ internal class WindowsDriver : ConsoleDriver int delay = START_DELAY; while (_isButtonPressed) { - // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. + // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. View? view = Application.WantContinuousButtonPressedView; if (view is null) @@ -890,7 +890,7 @@ internal class WindowsDriver : ConsoleDriver //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}"); if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. + // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. Application.Invoke (() => OnMouseEvent (me)); } } @@ -945,7 +945,7 @@ internal class WindowsDriver : ConsoleDriver if (_isButtonDoubleClicked || _isOneFingerDoubleClicked) { - // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. + // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. Application.MainLoop!.AddIdle ( () => { @@ -1017,7 +1017,7 @@ internal class WindowsDriver : ConsoleDriver if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { - // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. + // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. Application.MainLoop!.AddIdle ( () => { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 1cbe88211..12944b298 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -18,7 +18,7 @@ internal class WindowsMainLoop : IMainLoopDriver /// public EventHandler? WinChanged; - private readonly ConsoleDriver _consoleDriver; + private readonly IConsoleDriver _consoleDriver; private readonly ManualResetEventSlim _eventReady = new (false); // The records that we keep fetching @@ -29,13 +29,13 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private MainLoop? _mainLoop; - public WindowsMainLoop (ConsoleDriver consoleDriver) + public WindowsMainLoop (IConsoleDriver IConsoleDriver) { - _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); + _consoleDriver = IConsoleDriver ?? throw new ArgumentNullException (nameof (IConsoleDriver)); if (!ConsoleDriver.RunningUnitTests) { - _winConsole = ((WindowsDriver)consoleDriver).WinConsole; + _winConsole = ((WindowsDriver)IConsoleDriver).WinConsole; _winConsole!._mainLoop = this; } } diff --git a/Terminal.Gui/Drawing/Attribute.cs b/Terminal.Gui/Drawing/Attribute.cs index 32c948d2e..a36601ba1 100644 --- a/Terminal.Gui/Drawing/Attribute.cs +++ b/Terminal.Gui/Drawing/Attribute.cs @@ -17,7 +17,7 @@ public readonly record struct Attribute : IEqualityOperators new (Color.White, Color.Black); - /// The -specific color value. + /// The -specific color value. [JsonIgnore (Condition = JsonIgnoreCondition.Always)] internal int PlatformColor { get; init; } diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs index 16af046a7..5ce4e21df 100644 --- a/Terminal.Gui/Drawing/Cell.cs +++ b/Terminal.Gui/Drawing/Cell.cs @@ -2,7 +2,7 @@ /// /// Represents a single row/column in a Terminal.Gui rendering surface (e.g. and -/// ). +/// ). /// public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default) { diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 8261b156a..4a69a96b7 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -384,7 +384,7 @@ public class LineCanvas : IDisposable // TODO: Add other resolvers }; - private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects) + private Cell? GetCellForIntersects (IConsoleDriver? driver, IntersectionDefinition? [] intersects) { if (!intersects.Any ()) { @@ -404,7 +404,7 @@ public class LineCanvas : IDisposable return cell; } - private Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects) + private Rune? GetRuneForIntersects (IConsoleDriver? driver, IntersectionDefinition? [] intersects) { if (!intersects.Any ()) { @@ -727,7 +727,7 @@ public class LineCanvas : IDisposable internal Rune _thickV; protected IntersectionRuneResolver () { SetGlyphs (); } - public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects) + public Rune? GetRuneForIntersects (IConsoleDriver? driver, IntersectionDefinition? [] intersects) { bool useRounded = intersects.Any ( i => i?.Line.Length != 0 diff --git a/Terminal.Gui/Drawing/SixelToRender.cs b/Terminal.Gui/Drawing/SixelToRender.cs index dedd399ef..f9981f19f 100644 --- a/Terminal.Gui/Drawing/SixelToRender.cs +++ b/Terminal.Gui/Drawing/SixelToRender.cs @@ -2,7 +2,7 @@ /// /// Describes a request to render a given at a given . -/// Requires that the terminal and both support sixel. +/// Requires that the terminal and both support sixel. /// public class SixelToRender { diff --git a/Terminal.Gui/README.md b/Terminal.Gui/README.md index 5c22df0ac..a9ec75460 100644 --- a/Terminal.Gui/README.md +++ b/Terminal.Gui/README.md @@ -9,8 +9,8 @@ All files required to build the **Terminal.Gui** library (and NuGet package). - `Application\` - The core `Application` logic, including `Application.cs`, which is is a `static` class that provides the base 'application engine', `RunState`, and `MainLoop`. - `ConsoleDrivers\` - - `ConsoleDriver.cs` - Definition for the Console Driver API. - - Source files for the three `ConsoleDriver`-based drivers: .NET: `NetDriver`, Unix & Mac: `UnixDriver`, and Windows: `WindowsDriver`. + - `IConsoleDriver.cs` - Definition for the Console Driver API. + - Source files for the three `IConsoleDriver`-based drivers: .NET: `NetDriver`, Unix & Mac: `UnixDriver`, and Windows: `WindowsDriver`. - `Configuration\` - Classes related the `ConfigurationManager`. diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 2938b69bc..90152452f 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -43,7 +43,7 @@ public class TextFormatter set => _textDirection = EnableNeedsFormat (value); } - /// Draws the text held by to using the colors specified. + /// Draws the text held by to using the colors specified. /// /// Causes the text to be formatted (references ). Sets to /// false. @@ -59,7 +59,7 @@ public class TextFormatter Attribute normalColor, Attribute hotColor, Rectangle maximum = default, - ConsoleDriver? driver = null + IConsoleDriver? driver = null ) { // With this check, we protect against subclasses with overrides of Text (like Button) diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 27fcb065a..433822b4b 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -126,7 +126,7 @@ public partial class View : IDisposable, ISupportInitializeNotification /// Points to the current driver in use by the view, it is a convenience property for simplifying the development /// of new views. /// - public static ConsoleDriver? Driver => Application.Driver; + public static IConsoleDriver? Driver => Application.Driver; /// Initializes a new instance of . /// diff --git a/Terminal.Gui/Views/ColorPicker.Prompt.cs b/Terminal.Gui/Views/ColorPicker.Prompt.cs index 3f2372db9..0b79d3e9f 100644 --- a/Terminal.Gui/Views/ColorPicker.Prompt.cs +++ b/Terminal.Gui/Views/ColorPicker.Prompt.cs @@ -4,7 +4,7 @@ public partial class ColorPicker { /// /// Open a with two or , based on the - /// is false or true, respectively, for + /// is false or true, respectively, for /// and colors. /// /// The title to show in the dialog. diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index c46938890..01d78ee03 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -97,7 +97,7 @@ public class HorizontalAxis : Axis /// Text to render under the axis tick public override void DrawAxisLabel (GraphView graph, int screenPosition, string text) { - ConsoleDriver driver = Application.Driver; + IConsoleDriver driver = Application.Driver; int y = GetAxisYPosition (graph); graph.Move (screenPosition, y); diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 7bba8b714..6da4f2125 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -667,7 +667,7 @@ public class MenuBar : View, IDesignable return true; } - /// Gets the superview location offset relative to the location. + /// Gets the superview location offset relative to the location. /// The location offset. internal Point GetScreenOffset () { diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index 4dd947734..ffc806880 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -29,7 +29,7 @@ public class TableStyle /// /// True to invert the colors of the first symbol of the selected cell in the . This gives - /// the appearance of a cursor for when the doesn't otherwise show this + /// the appearance of a cursor for when the doesn't otherwise show this /// public bool InvertSelectedCellFirstCharacter { get; set; } = false; diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 468b851f6..68c5a037e 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -1277,7 +1277,7 @@ public class TableView : View, IDesignable /// /// Override to provide custom multi colouring to cells. Use to with - /// . The driver will already be in the correct place when rendering and you + /// . The driver will already be in the correct place when rendering and you /// must render the full or the view will not look right. For simpler provision of color use /// For changing the content that is rendered use /// @@ -1335,7 +1335,7 @@ public class TableView : View, IDesignable /// internal int GetHeaderHeightIfAny () { return ShouldRenderHeaders () ? GetHeaderHeight () : 0; } - private void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch) + private void AddRuneAt (IConsoleDriver d, int col, int row, Rune ch) { Move (col, row); d?.AddRune (ch); diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index e2d220ace..e7a5eb4ca 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -73,7 +73,7 @@ internal class Branch where T : class /// /// /// - public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth) + public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth) { List cells = new (); int? indexOfExpandCollapseSymbol = null; @@ -291,7 +291,7 @@ internal class Branch where T : class /// /// /// - public Rune GetExpandableSymbol (ConsoleDriver driver) + public Rune GetExpandableSymbol (IConsoleDriver driver) { Rune leafSymbol = tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' '; @@ -313,7 +313,7 @@ internal class Branch where T : class /// line body). /// /// - public virtual int GetWidth (ConsoleDriver driver) + public virtual int GetWidth (IConsoleDriver driver) { return GetLinePrefix (driver).Sum (r => r.GetColumns ()) + GetExpandableSymbol (driver).GetColumns () + (tree.AspectGetter (Model) ?? "").Length; @@ -408,7 +408,7 @@ internal class Branch where T : class /// /// /// - internal IEnumerable GetLinePrefix (ConsoleDriver driver) + internal IEnumerable GetLinePrefix (IConsoleDriver driver) { // If not showing line branches or this is a root object. if (!tree.Style.ShowBranchLines) @@ -453,7 +453,7 @@ internal class Branch where T : class /// /// /// - internal bool IsHitOnExpandableSymbol (ConsoleDriver driver, int x) + internal bool IsHitOnExpandableSymbol (IConsoleDriver driver, int x) { // if leaf node then we cannot expand if (!CanExpand ()) diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index 056086c64..f66463664 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -1000,7 +1000,7 @@ public class GraphViewExample : Scenario protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn) { - ConsoleDriver driver = Application.Driver; + IConsoleDriver driver = Application.Driver; int x = start.X; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index ba7e3f26d..fffa989f2 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -136,7 +136,7 @@ public class UICatalogApp // Process command line args // "UICatalog [--driver ] [--benchmark] [scenario name]" // If no driver is provided, the default driver is used. - Option driverOption = new Option ("--driver", "The ConsoleDriver to use.").FromAmong ( + Option driverOption = new Option ("--driver", "The IConsoleDriver to use.").FromAmong ( Application.GetDriverTypes () .Select (d => d!.Name) .ToArray () diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 5e3bf43a2..ab44e66ef 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -248,7 +248,7 @@ public class ApplicationTests [InlineData (typeof (CursesDriver))] public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driverName: driverType.Name); Assert.NotNull (Application.Driver); Assert.NotEqual (driver, Application.Driver); @@ -630,7 +630,7 @@ public class ApplicationTests driver.Cols = 100; driver.Rows = 30; - // ConsoleDriver.Screen isn't assignable + // IConsoleDriver.Screen isn't assignable //driver.Screen = new (0, 0, driver.Cols, Rows); Assert.Equal (new (0, 0, 100, 30), driver.Screen); Assert.NotEqual (new (0, 0, 100, 30), Application.Screen); diff --git a/UnitTests/ConsoleDrivers/AddRuneTests.cs b/UnitTests/ConsoleDrivers/AddRuneTests.cs index 6769db830..5c753386e 100644 --- a/UnitTests/ConsoleDrivers/AddRuneTests.cs +++ b/UnitTests/ConsoleDrivers/AddRuneTests.cs @@ -25,7 +25,7 @@ public class AddRuneTests [InlineData (typeof (CursesDriver))] public void AddRune (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); driver.Rows = 25; diff --git a/UnitTests/ConsoleDrivers/ClipRegionTests.cs b/UnitTests/ConsoleDrivers/ClipRegionTests.cs index c85f7fec8..9e01f83ee 100644 --- a/UnitTests/ConsoleDrivers/ClipRegionTests.cs +++ b/UnitTests/ConsoleDrivers/ClipRegionTests.cs @@ -24,7 +24,7 @@ public class ClipRegionTests [InlineData (typeof (CursesDriver))] public void AddRune_Is_Clipped (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); Application.Driver!.Rows = 25; Application.Driver!.Cols = 80; @@ -62,7 +62,7 @@ public class ClipRegionTests [InlineData (typeof (CursesDriver))] public void Clip_Set_To_Empty_AllInvalid (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); // Define a clip rectangle @@ -92,7 +92,7 @@ public class ClipRegionTests [InlineData (typeof (CursesDriver))] public void IsValidLocation (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); Application.Driver!.Rows = 10; Application.Driver!.Cols = 10; diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index 8ecc97807..d75a56639 100644 --- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -25,7 +25,7 @@ public class ConsoleDriverTests [InlineData (typeof (CursesDriver))] public void End_Cleans_Up (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); driver.End (); } @@ -34,7 +34,7 @@ public class ConsoleDriverTests [InlineData (typeof (FakeDriver))] public void FakeDriver_MockKeyPresses (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); var text = "MockKeyPresses"; @@ -85,7 +85,7 @@ public class ConsoleDriverTests [InlineData (typeof (FakeDriver))] public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); Toplevel top = new (); @@ -124,7 +124,7 @@ public class ConsoleDriverTests [InlineData (typeof (CursesDriver))] public void Init_Inits (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); MainLoop ml = driver.Init (); Assert.NotNull (ml); Assert.NotNull (driver.Clipboard); @@ -140,7 +140,7 @@ public class ConsoleDriverTests //[InlineData (typeof (FakeDriver))] //public void FakeDriver_MockKeyPresses_Press_AfterTimeOut (Type driverType) //{ - // var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + // var driver = (IConsoleDriver)Activator.CreateInstance (driverType); // Application.Init (driver); // // Simulating pressing of QuitKey after a short period of time @@ -201,7 +201,7 @@ public class ConsoleDriverTests [InlineData (typeof (CursesDriver))] public void TerminalResized_Simulation (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver?.Init (); driver.Cols = 80; driver.Rows = 25; diff --git a/UnitTests/ConsoleDrivers/ContentsTests.cs b/UnitTests/ConsoleDrivers/ContentsTests.cs index 0dd4be3bf..d0f2d83ff 100644 --- a/UnitTests/ConsoleDrivers/ContentsTests.cs +++ b/UnitTests/ConsoleDrivers/ContentsTests.cs @@ -24,7 +24,7 @@ public class ContentsTests //[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed public void AddStr_Combining_Character_1st_Column (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); var expected = "\u0301!"; driver.AddStr ("\u0301!"); // acute accent + exclamation mark @@ -42,7 +42,7 @@ public class ContentsTests //[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed public void AddStr_With_Combining_Characters (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); var acuteaccent = new Rune (0x0301); // Combining acute accent (é) @@ -98,7 +98,7 @@ public class ContentsTests [InlineData (typeof (CursesDriver))] public void Move_Bad_Coordinates (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); Assert.Equal (0, driver.Col); diff --git a/UnitTests/ConsoleDrivers/DriverColorTests.cs b/UnitTests/ConsoleDrivers/DriverColorTests.cs index 8bebace1e..856e0481a 100644 --- a/UnitTests/ConsoleDrivers/DriverColorTests.cs +++ b/UnitTests/ConsoleDrivers/DriverColorTests.cs @@ -17,7 +17,7 @@ public class DriverColorTests [InlineData (typeof (CursesDriver))] public void Force16Colors_Sets (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); driver.Force16Colors = true; @@ -35,7 +35,7 @@ public class DriverColorTests [InlineData (typeof (CursesDriver))] public void SetColors_Changes_Colors (Type driverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor); @@ -63,7 +63,7 @@ public class DriverColorTests [InlineData (typeof (CursesDriver), true)] public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); Assert.Equal (expectedSetting, driver.SupportsTrueColor); diff --git a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs index 2380ed83e..9720d51b5 100644 --- a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs +++ b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -17,7 +17,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); var idleHandlerInvoked = false; @@ -47,7 +47,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); var callbackInvoked = false; @@ -83,7 +83,7 @@ public class MainLoopDriverTests Type mainLoopDriverType ) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -107,7 +107,7 @@ public class MainLoopDriverTests Type mainLoopDriverType ) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -130,7 +130,7 @@ public class MainLoopDriverTests Type mainLoopDriverType ) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -151,7 +151,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -182,7 +182,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -201,7 +201,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -223,7 +223,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -241,7 +241,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); @@ -261,7 +261,7 @@ public class MainLoopDriverTests //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driverType, Type mainLoopDriverType) { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); var idleHandlerInvoked = false; @@ -287,7 +287,7 @@ public class MainLoopDriverTests //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] //public void MainLoop_Invoke_ValidAction_RunsAction (Type driverType, Type mainLoopDriverType) //{ - // var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + // var driver = (IConsoleDriver)Activator.CreateInstance (driverType); // var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); // var mainLoop = new MainLoop (mainLoopDriver); // var actionInvoked = false; diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 19a532ad4..3f32e35b5 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -25,21 +25,21 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute /// /// If true, Application.Init will be called Before the test runs. /// - /// Determines which ConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver) + /// Determines which IConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver) /// will be used when Application.Init is called. If null FakeDriver will be used. Only valid if /// is true. /// /// /// If true, will force the use of . Only valid if - /// == and is true. + /// == and is true. /// /// /// Only valid if is true. Only - /// valid if == and is true. + /// valid if == and is true. /// /// /// Only valid if is true. Only valid if - /// == and is true. + /// == and is true. /// /// Determines what config file locations will load from. /// If true and is true, the test will fail. @@ -135,7 +135,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute View.Instances.Clear (); } #endif - Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType)); + Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType)); } } @@ -251,12 +251,12 @@ internal partial class TestHelpers /// . /// /// - /// The ConsoleDriver to use. If null will be used. + /// The IConsoleDriver to use. If null will be used. /// public static void AssertDriverAttributesAre ( string expectedLook, ITestOutputHelper output, - ConsoleDriver driver = null, + IConsoleDriver driver = null, params Attribute [] expectedAttributes ) { @@ -321,12 +321,12 @@ internal partial class TestHelpers /// Asserts that the driver contents match the expected contents, optionally ignoring any trailing whitespace. /// /// - /// The ConsoleDriver to use. If null will be used. + /// The IConsoleDriver to use. If null will be used. /// public static void AssertDriverContentsAre ( string expectedLook, ITestOutputHelper output, - ConsoleDriver driver = null, + IConsoleDriver driver = null, bool ignoreLeadingWhitespace = false ) { @@ -367,12 +367,12 @@ internal partial class TestHelpers /// /// /// - /// The ConsoleDriver to use. If null will be used. + /// The IConsoleDriver to use. If null will be used. /// public static Rectangle AssertDriverContentsWithFrameAre ( string expectedLook, ITestOutputHelper output, - ConsoleDriver driver = null + IConsoleDriver driver = null ) { List> lines = new (); @@ -625,7 +625,7 @@ internal partial class TestHelpers /// /// if null uses /// - internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors) + internal static void AssertDriverUsedColors (IConsoleDriver driver = null, params Attribute [] expectedColors) { driver ??= Application.Driver; Cell [,] contents = driver.Contents; From 1d636959bc4523e2fef7da2b84f42cdeb5455d23 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 24 Nov 2024 11:14:07 +0000 Subject: [PATCH 142/151] Fix enumerating drivers --- Terminal.Gui/Application/Application.Initialization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 31cfa09df..c58e1bf92 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -193,7 +193,7 @@ public static partial class Application // Initialization (Init/Shutdown) { foreach (Type? type in asm.GetTypes ()) { - if (type.IsSubclassOf (typeof (IConsoleDriver)) && !type.IsAbstract) + if (typeof (IConsoleDriver).IsAssignableFrom (type) && !type.IsAbstract && type.IsClass) { driverTypes.Add (type); } From e19bffbc010d7e745d1445831034714fcb24f723 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 24 Nov 2024 11:16:35 +0000 Subject: [PATCH 143/151] Move IConsoleDriver to its own file --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 287 ----------------- Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs | 295 ++++++++++++++++++ 2 files changed, 295 insertions(+), 287 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index c4e0e385c..f64de17fc 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -4,293 +4,6 @@ using System.Diagnostics; namespace Terminal.Gui; -public interface IConsoleDriver -{ - /// Get the operating system clipboard. - IClipboard? Clipboard { get; } - - /// Gets the location and size of the terminal screen. - Rectangle Screen { get; } - - /// - /// Gets or sets the clip rectangle that and are subject - /// to. - /// - /// The rectangle describing the of region. - Region? Clip { get; set; } - - /// - /// Gets the column last set by . and are used by - /// and to determine where to add content. - /// - int Col { get; } - - /// The number of columns visible in the terminal. - int Cols { get; set; } - - /// - /// The contents of the application output. The driver outputs this buffer to the terminal when - /// is called. - /// The format of the array is rows, columns. The first index is the row, the second index is the column. - /// - Cell [,]? Contents { get; set; } - - /// The leftmost column in the terminal. - int Left { get; set; } - - /// - /// Gets the row last set by . and are used by - /// and to determine where to add content. - /// - int Row { get; } - - /// The number of rows visible in the terminal. - int Rows { get; set; } - - /// The topmost row in the terminal. - int Top { get; set; } - - /// Gets whether the supports TrueColor output. - bool SupportsTrueColor { get; } - - /// - /// 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. - /// - /// - bool Force16Colors { get; set; } - - /// - /// The that will be used for the next or - /// call. - /// - Attribute CurrentAttribute { get; set; } - - /// Returns the name of the driver and relevant library version information. - /// - string GetVersionInfo (); - - /// - /// Provide proper writing to send escape sequence recognized by the . - /// - /// - void WriteRaw (string ansi); - - /// 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. - /// - bool IsRuneSupported (Rune rune); - - /// Tests whether the specified coordinate are valid for drawing. - /// The column. - /// The row. - /// - /// if the coordinate is outside the screen bounds or outside of . - /// otherwise. - /// - bool IsValidLocation (int col, int row); - - /// 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. - /// - bool IsValidLocation (Rune rune, int col, int 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. - void Move (int col, int row); - - /// 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. - void AddRune (Rune 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. - void AddRune (char 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. - void AddStr (string str); - - /// 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 - void FillRect (Rectangle rect, Rune rune = default); - - /// - /// Fills the specified rectangle with the specified . This method is a convenience method - /// that calls . - /// - /// - /// - void FillRect (Rectangle rect, char c); - - /// Clears the of the driver. - void ClearContents (); - - /// - /// Raised each time is called. For benchmarking. - /// - event EventHandler? ClearedContents; - - /// - /// Sets as dirty for situations where views - /// don't need layout and redrawing, but just refresh the screen. - /// - void SetContentsAsDirty (); - - /// Determines if the terminal cursor should be visible or not and sets it accordingly. - /// upon success - bool EnsureCursorVisibility (); - - /// Gets the terminal cursor visibility. - /// The current - /// upon success - bool GetCursorVisibility (out CursorVisibility visibility); - - /// Called when the terminal size changes. Fires the event. - /// - void OnSizeChanged (SizeChangedEventArgs args); - - /// Updates the screen to reflect all the changes that have been done to the display buffer - void Refresh (); - - /// - /// Raised each time is called. For benchmarking. - /// - event EventHandler>? Refreshed; - - /// Sets the terminal cursor visibility. - /// The wished - /// upon success - bool SetCursorVisibility (CursorVisibility visibility); - - /// The event fired when the terminal is resized. - event EventHandler? SizeChanged; - - /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. - /// This is only implemented in . - void Suspend (); - - /// Sets the position of the terminal cursor to and . - void UpdateCursor (); - - /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. - /// if any updates to the screen were made. - bool UpdateScreen (); - - /// Initializes the driver - /// Returns an instance of using the for the driver. - MainLoop Init (); - - /// Ends the execution of the console driver. - void End (); - - /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. - /// Implementations should call base.SetAttribute(c). - /// C. - Attribute SetAttribute (Attribute c); - - /// Gets the current . - /// The current attribute. - Attribute GetAttribute (); - - /// Makes an . - /// The foreground color. - /// The background color. - /// The attribute for the foreground and background colors. - Attribute MakeColor (in Color foreground, in Color background); - - /// Event fired when a mouse event occurs. - event EventHandler? MouseEvent; - - /// Called when a mouse event occurs. Fires the event. - /// - void OnMouseEvent (MouseEventArgs a); - - /// Event fired when a key is pressed down. This is a precursor to . - event EventHandler? KeyDown; - - /// - /// Called when a key is pressed down. Fires the event. This is a precursor to - /// . - /// - /// - void OnKeyDown (Key a); - - /// Event fired when a key is released. - /// - /// Drivers that do not support key release events will fire this event after processing is - /// complete. - /// - event EventHandler? KeyUp; - - /// Called when a key is released. Fires the event. - /// - /// Drivers that do not support key release events will call this method after processing - /// is complete. - /// - /// - void OnKeyUp (Key a); - - /// Simulates a key press. - /// The key character. - /// The key. - /// If simulates the Shift key being pressed. - /// If simulates the Alt key being pressed. - /// If simulates the Ctrl key being pressed. - void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); -} - /// Base class for Terminal.Gui ConsoleDriver implementations. /// /// There are currently four implementations: - (for Unix and Mac) - diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs new file mode 100644 index 000000000..d5e9cd009 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs @@ -0,0 +1,295 @@ +#nullable enable +namespace Terminal.Gui; + +/// Base interface for Terminal.Gui ConsoleDriver implementations. +/// +/// There are currently four implementations: - (for Unix and Mac) - +/// - that uses the .NET Console API - +/// for unit testing. +/// +public interface IConsoleDriver +{ + /// Get the operating system clipboard. + IClipboard? Clipboard { get; } + + /// Gets the location and size of the terminal screen. + Rectangle Screen { get; } + + /// + /// Gets or sets the clip rectangle that and are subject + /// to. + /// + /// The rectangle describing the of region. + Region? Clip { get; set; } + + /// + /// Gets the column last set by . and are used by + /// and to determine where to add content. + /// + int Col { get; } + + /// The number of columns visible in the terminal. + int Cols { get; set; } + + /// + /// The contents of the application output. The driver outputs this buffer to the terminal when + /// is called. + /// The format of the array is rows, columns. The first index is the row, the second index is the column. + /// + Cell [,]? Contents { get; set; } + + /// The leftmost column in the terminal. + int Left { get; set; } + + /// + /// Gets the row last set by . and are used by + /// and to determine where to add content. + /// + int Row { get; } + + /// The number of rows visible in the terminal. + int Rows { get; set; } + + /// The topmost row in the terminal. + int Top { get; set; } + + /// Gets whether the supports TrueColor output. + bool SupportsTrueColor { get; } + + /// + /// 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. + /// + /// + bool Force16Colors { get; set; } + + /// + /// The that will be used for the next or + /// call. + /// + Attribute CurrentAttribute { get; set; } + + /// Returns the name of the driver and relevant library version information. + /// + string GetVersionInfo (); + + /// + /// Provide proper writing to send escape sequence recognized by the . + /// + /// + void WriteRaw (string ansi); + + /// 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. + /// + bool IsRuneSupported (Rune rune); + + /// Tests whether the specified coordinate are valid for drawing. + /// The column. + /// The row. + /// + /// if the coordinate is outside the screen bounds or outside of . + /// otherwise. + /// + bool IsValidLocation (int col, int row); + + /// 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. + /// + bool IsValidLocation (Rune rune, int col, int 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. + void Move (int col, int row); + + /// 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. + void AddRune (Rune 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. + void AddRune (char 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. + void AddStr (string str); + + /// 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 + void FillRect (Rectangle rect, Rune rune = default); + + /// + /// Fills the specified rectangle with the specified . This method is a convenience method + /// that calls . + /// + /// + /// + void FillRect (Rectangle rect, char c); + + /// Clears the of the driver. + void ClearContents (); + + /// + /// Raised each time is called. For benchmarking. + /// + event EventHandler? ClearedContents; + + /// + /// Sets as dirty for situations where views + /// don't need layout and redrawing, but just refresh the screen. + /// + void SetContentsAsDirty (); + + /// Determines if the terminal cursor should be visible or not and sets it accordingly. + /// upon success + bool EnsureCursorVisibility (); + + /// Gets the terminal cursor visibility. + /// The current + /// upon success + bool GetCursorVisibility (out CursorVisibility visibility); + + /// Called when the terminal size changes. Fires the event. + /// + void OnSizeChanged (SizeChangedEventArgs args); + + /// Updates the screen to reflect all the changes that have been done to the display buffer + void Refresh (); + + /// + /// Raised each time is called. For benchmarking. + /// + event EventHandler>? Refreshed; + + /// Sets the terminal cursor visibility. + /// The wished + /// upon success + bool SetCursorVisibility (CursorVisibility visibility); + + /// The event fired when the terminal is resized. + event EventHandler? SizeChanged; + + /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. + /// This is only implemented in . + void Suspend (); + + /// Sets the position of the terminal cursor to and . + void UpdateCursor (); + + /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. + /// if any updates to the screen were made. + bool UpdateScreen (); + + /// Initializes the driver + /// Returns an instance of using the for the driver. + MainLoop Init (); + + /// Ends the execution of the console driver. + void End (); + + /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. + /// Implementations should call base.SetAttribute(c). + /// C. + Attribute SetAttribute (Attribute c); + + /// Gets the current . + /// The current attribute. + Attribute GetAttribute (); + + /// Makes an . + /// The foreground color. + /// The background color. + /// The attribute for the foreground and background colors. + Attribute MakeColor (in Color foreground, in Color background); + + /// Event fired when a mouse event occurs. + event EventHandler? MouseEvent; + + /// Called when a mouse event occurs. Fires the event. + /// + void OnMouseEvent (MouseEventArgs a); + + /// Event fired when a key is pressed down. This is a precursor to . + event EventHandler? KeyDown; + + /// + /// Called when a key is pressed down. Fires the event. This is a precursor to + /// . + /// + /// + void OnKeyDown (Key a); + + /// Event fired when a key is released. + /// + /// Drivers that do not support key release events will fire this event after processing is + /// complete. + /// + event EventHandler? KeyUp; + + /// Called when a key is released. Fires the event. + /// + /// Drivers that do not support key release events will call this method after processing + /// is complete. + /// + /// + void OnKeyUp (Key a); + + /// Simulates a key press. + /// The key character. + /// The key. + /// If simulates the Shift key being pressed. + /// If simulates the Alt key being pressed. + /// If simulates the Ctrl key being pressed. + void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); +} From 9f1d0b3c2e02c75123ac26302f9b91ece8f4505f Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 24 Nov 2024 11:39:38 +0000 Subject: [PATCH 144/151] Fix parameter naming updated in error as part of refactor --- Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 2 +- Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs | 4 ++-- Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs | 8 ++++---- .../ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index ebc3ae684..6b6789fc4 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -4,7 +4,7 @@ internal class FakeMainLoop : IMainLoopDriver { public Action MockKeyPressed; - public FakeMainLoop (IConsoleDriver IConsoleDriver = null) + public FakeMainLoop (IConsoleDriver consoleDriver = null) { // No implementation needed for FakeMainLoop } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index c0dcaffb4..9ba56d673 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -14,9 +14,9 @@ internal class NetEvents : IDisposable #if PROCESS_REQUEST bool _neededProcessRequest; #endif - public NetEvents (IConsoleDriver IConsoleDriver) + public NetEvents (IConsoleDriver consoleDriver) { - _consoleDriver = IConsoleDriver ?? throw new ArgumentNullException (nameof (IConsoleDriver)); + _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); _inputReadyCancellationTokenSource = new (); Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 7632c49fb..32e7c53db 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -25,15 +25,15 @@ internal class NetMainLoop : IMainLoopDriver /// Initializes the class with the console driver. /// Passing a IConsoleDriver is provided to capture windows resizing. - /// The console driver used by this Net main loop. + /// The console driver used by this Net main loop. /// - public NetMainLoop (IConsoleDriver IConsoleDriver) + public NetMainLoop (IConsoleDriver consoleDriver) { - ArgumentNullException.ThrowIfNull (IConsoleDriver); + ArgumentNullException.ThrowIfNull (consoleDriver); if (!ConsoleDriver.RunningUnitTests) { - _netEvents = new (IConsoleDriver); + _netEvents = new (consoleDriver); } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 12944b298..f1ffafdca 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -29,13 +29,13 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private MainLoop? _mainLoop; - public WindowsMainLoop (IConsoleDriver IConsoleDriver) + public WindowsMainLoop (IConsoleDriver consoleDriver) { - _consoleDriver = IConsoleDriver ?? throw new ArgumentNullException (nameof (IConsoleDriver)); + _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); if (!ConsoleDriver.RunningUnitTests) { - _winConsole = ((WindowsDriver)IConsoleDriver).WinConsole; + _winConsole = ((WindowsDriver)consoleDriver).WinConsole; _winConsole!._mainLoop = this; } } From d39a940955e065ef298ca73a730c1f021419505e Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 25 Nov 2024 20:17:04 -0700 Subject: [PATCH 145/151] Added Applicaton.ClearScreenNextIteration --- Terminal.Gui/Application/Application.Run.cs | 5 +++++ Terminal.Gui/Application/Application.Screen.cs | 9 +++++++++ Terminal.Gui/Application/Application.cs | 2 ++ Terminal.Gui/View/View.Layout.cs | 9 ++++++++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 7511f8216..ebba96404 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -505,6 +505,11 @@ public static partial class Application // Run (Begin, Run, End, Stop) { bool neededLayout = View.Layout (TopLevels.Reverse (), Screen.Size); + if (ClearScreenNextIteration) + { + forceDraw = true; + ClearScreenNextIteration = false; + } if (forceDraw) { Driver?.ClearContents (); diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs index c5bf6d6fd..9edd29313 100644 --- a/Terminal.Gui/Application/Application.Screen.cs +++ b/Terminal.Gui/Application/Application.Screen.cs @@ -67,4 +67,13 @@ public static partial class Application // Screen related stuff return true; } + + /// + /// Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration. + /// + /// + /// This is typicall set to true when a View's changes and that view has no + /// SuperView (e.g. when is moved or resized. + /// + public static bool ClearScreenNextIteratio { get; set; } } diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index d9e6c68d9..85a234cef 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -215,6 +215,8 @@ public static partial class Application Navigation = null; + ClearScreenNextIteration = false; + AddApplicationKeyBindings (); // Reset synchronization context to allow the user to run async/await, diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index 1b37f9725..833d5bdb2 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -557,7 +557,14 @@ public partial class View // Layout APIs SetTitleTextFormatterSize (); } - SuperView?.SetNeedsDraw (); + if (SuperView is { }) + { + SuperView?.SetNeedsDraw (); + } + else + { + // Application.ClearScreenNextIteration = true; + } } if (TextFormatter.ConstrainToWidth is null) From 344e55bc5d10775acb2833821337bd0a3bdf90bb Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 25 Nov 2024 20:18:14 -0700 Subject: [PATCH 146/151] fixed one other case --- Terminal.Gui/Application/Application.Screen.cs | 2 +- Terminal.Gui/View/View.Layout.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs index 9edd29313..5a8f68407 100644 --- a/Terminal.Gui/Application/Application.Screen.cs +++ b/Terminal.Gui/Application/Application.Screen.cs @@ -75,5 +75,5 @@ public static partial class Application // Screen related stuff /// This is typicall set to true when a View's changes and that view has no /// SuperView (e.g. when is moved or resized. /// - public static bool ClearScreenNextIteratio { get; set; } + public static bool ClearScreenNextIteration { get; set; } } diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index 833d5bdb2..b0991711b 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -563,7 +563,7 @@ public partial class View // Layout APIs } else { - // Application.ClearScreenNextIteration = true; + Application.ClearScreenNextIteration = true; } } From fd5a2b6b23c4a7329e289f4fc071affb82ab3380 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 25 Nov 2024 20:48:18 -0700 Subject: [PATCH 147/151] fixed Run cases --- Terminal.Gui/Application/Application.Run.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index ebba96404..2a91ac8df 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -693,6 +693,6 @@ public static partial class Application // Run (Begin, Run, End, Stop) runState.Toplevel = null; runState.Dispose (); - LayoutAndDraw (); + LayoutAndDraw (true); } } From 88f3d05a19806e442aafc3e37d75fcebdd76a4d7 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 25 Nov 2024 20:53:20 -0700 Subject: [PATCH 148/151] Fixed more cases --- CommunityToolkitExample/LoginView.cs | 1 + Terminal.Gui/Application/Application.Keyboard.cs | 2 +- Terminal.Gui/Application/Application.Run.cs | 1 - Terminal.Gui/Application/Application.Screen.cs | 2 +- Terminal.Gui/View/View.cs | 9 ++++++++- Terminal.Gui/Views/Menu/Menu.cs | 3 +-- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs index 7f8fbe53d..726ab009f 100644 --- a/CommunityToolkitExample/LoginView.cs +++ b/CommunityToolkitExample/LoginView.cs @@ -59,6 +59,7 @@ internal partial class LoginView : IRecipient> } } SetText(); + // BUGBUG: This should not be needed: Application.LayoutAndDraw (); } diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 97b566166..16d167f34 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -205,7 +205,7 @@ public static partial class Application // Keyboard handling Command.Refresh, static () => { - LayoutAndDraw (); + LayoutAndDraw (true); return true; } diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 2a91ac8df..5f6b5ddba 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -508,7 +508,6 @@ public static partial class Application // Run (Begin, Run, End, Stop) if (ClearScreenNextIteration) { forceDraw = true; - ClearScreenNextIteration = false; } if (forceDraw) { diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs index 5a8f68407..5bd74fb1a 100644 --- a/Terminal.Gui/Application/Application.Screen.cs +++ b/Terminal.Gui/Application/Application.Screen.cs @@ -63,7 +63,7 @@ public static partial class Application // Screen related stuff t.SetNeedsLayout (); } - LayoutAndDraw (); + LayoutAndDraw (true); return true; } diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 27fcb065a..e1470d7b1 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -369,7 +369,14 @@ public partial class View : IDisposable, ISupportInitializeNotification SetNeedsLayout (); SuperView?.SetNeedsLayout (); SetNeedsDraw (); - SuperView?.SetNeedsDraw (); + if (SuperView is { }) + { + SuperView?.SetNeedsDraw (); + } + else + { + Application.ClearScreenNextIteration = true; + } } } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index fdfab4d98..ac3d61797 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -608,8 +608,7 @@ internal sealed class Menu : View Application.UngrabMouse (); _host.CloseAllMenus (); - Application.Driver!.ClearContents (); - Application.LayoutAndDraw (); + Application.LayoutAndDraw (true); _host.Run (action); } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 7bba8b714..3816a1a29 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -1117,7 +1117,7 @@ public class MenuBar : View, IDesignable Application.UngrabMouse (); CloseAllMenus (); - Application.LayoutAndDraw (); + Application.LayoutAndDraw (true); _openedByAltKey = true; return Run (item.Action); From 03625877998d81b0e42f2c385a680da7d86a0907 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 26 Nov 2024 09:42:43 -0700 Subject: [PATCH 149/151] Added unit tests --- Terminal.Gui/Application/Application.Run.cs | 1 + .../Application/ApplicationScreenTests.cs | 67 +++++++++++++++++++ bench.json | 52 -------------- 3 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 UnitTests/Application/ApplicationScreenTests.cs delete mode 100644 bench.json diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 5f6b5ddba..2a91ac8df 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -508,6 +508,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) if (ClearScreenNextIteration) { forceDraw = true; + ClearScreenNextIteration = false; } if (forceDraw) { diff --git a/UnitTests/Application/ApplicationScreenTests.cs b/UnitTests/Application/ApplicationScreenTests.cs new file mode 100644 index 000000000..1ea896208 --- /dev/null +++ b/UnitTests/Application/ApplicationScreenTests.cs @@ -0,0 +1,67 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ApplicationTests.NavigationTests; + +public class ApplicationScreenTests (ITestOutputHelper output) +{ + [Fact] + public void ClearScreenNextIteration_Resets_To_False_After_LayoutAndDraw () + { + // Arrange + Application.Init (); + + // Act + Application.ClearScreenNextIteration = true; + Application.LayoutAndDraw (); + + // Assert + Assert.False (Application.ClearScreenNextIteration); + + // Cleanup + Application.ResetState (true); + } + + [Fact] + [SetupFakeDriver] + public void ClearContents_Called_When_Top_Frame_Changes () + { + // Arrange + Application.Init (); + Application.Top = new Toplevel (); + Application.TopLevels.Push (Application.Top); + + int clearedContentsRaised = 0; + + Application.Driver!.ClearedContents += (e, a) => clearedContentsRaised++; + + // Act + Application.LayoutAndDraw (); + + // Assert + Assert.Equal (0, clearedContentsRaised); + + // Act + Application.Top.SetNeedsLayout (); + Application.LayoutAndDraw (); + + // Assert + Assert.Equal (0, clearedContentsRaised); + + // Act + Application.Top.X = 1; + Application.LayoutAndDraw (); + + // Assert + Assert.Equal (1, clearedContentsRaised); + + // Act + Application.Top.Width = 10; + Application.LayoutAndDraw (); + + // Assert + Assert.Equal (2, clearedContentsRaised); + + // Cleanup + Application.ResetState (true); + } +} diff --git a/bench.json b/bench.json deleted file mode 100644 index 52a38240e..000000000 --- a/bench.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "Scenario": "Adornments Demo", - "Duration": "00:00:00.1805368", - "IterationCount": 501, - "ClearedContentCount": 0, - "RefreshedCount": 503, - "UpdatedCount": 1, - "DrawCompleteCount": 82, - "LaidOutCount": 82 - }, - { - "Scenario": "All Views Tester", - "Duration": "00:00:00.1070009", - "IterationCount": 501, - "ClearedContentCount": 0, - "RefreshedCount": 503, - "UpdatedCount": 1, - "DrawCompleteCount": 103, - "LaidOutCount": 182 - }, - { - "Scenario": "Animation", - "Duration": "00:00:00.0675802", - "IterationCount": 501, - "ClearedContentCount": 0, - "RefreshedCount": 503, - "UpdatedCount": 1, - "DrawCompleteCount": 4, - "LaidOutCount": 4 - }, - { - "Scenario": "Arrangement", - "Duration": "00:00:00.1284709", - "IterationCount": 501, - "ClearedContentCount": 0, - "RefreshedCount": 503, - "UpdatedCount": 1, - "DrawCompleteCount": 123, - "LaidOutCount": 123 - }, - { - "Scenario": "ASCIICustomButtonTest", - "Duration": "00:00:01.0613372", - "IterationCount": 30, - "ClearedContentCount": 0, - "RefreshedCount": 32, - "UpdatedCount": 31, - "DrawCompleteCount": 4185, - "LaidOutCount": 2852 - } -] \ No newline at end of file From fab08af932c4b39a8c83901e8e9f203e34054b77 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 26 Nov 2024 09:59:14 -0700 Subject: [PATCH 150/151] Tweaked unit tests --- UnitTests/Application/ApplicationScreenTests.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/UnitTests/Application/ApplicationScreenTests.cs b/UnitTests/Application/ApplicationScreenTests.cs index 1ea896208..f5c2bef85 100644 --- a/UnitTests/Application/ApplicationScreenTests.cs +++ b/UnitTests/Application/ApplicationScreenTests.cs @@ -26,7 +26,6 @@ public class ApplicationScreenTests (ITestOutputHelper output) public void ClearContents_Called_When_Top_Frame_Changes () { // Arrange - Application.Init (); Application.Top = new Toplevel (); Application.TopLevels.Push (Application.Top); @@ -38,28 +37,28 @@ public class ApplicationScreenTests (ITestOutputHelper output) Application.LayoutAndDraw (); // Assert - Assert.Equal (0, clearedContentsRaised); + Assert.Equal (1, clearedContentsRaised); // Act Application.Top.SetNeedsLayout (); Application.LayoutAndDraw (); // Assert - Assert.Equal (0, clearedContentsRaised); + Assert.Equal (1, clearedContentsRaised); // Act Application.Top.X = 1; Application.LayoutAndDraw (); // Assert - Assert.Equal (1, clearedContentsRaised); + Assert.Equal (2, clearedContentsRaised); // Act Application.Top.Width = 10; Application.LayoutAndDraw (); // Assert - Assert.Equal (2, clearedContentsRaised); + Assert.Equal (3, clearedContentsRaised); // Cleanup Application.ResetState (true); From 774e60343897beb1f1edf9d29373c030a7bc09d4 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 26 Nov 2024 10:18:14 -0700 Subject: [PATCH 151/151] Tweaked unit tests more --- UnitTests/Application/ApplicationScreenTests.cs | 8 +++++--- UnitTests/Configuration/ConfigPropertyTests.cs | 2 ++ UnitTests/Drawing/SixelEncoderTests.cs | 2 +- UnitTests/LocalPackagesTests.cs | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/UnitTests/Application/ApplicationScreenTests.cs b/UnitTests/Application/ApplicationScreenTests.cs index f5c2bef85..c6a220e52 100644 --- a/UnitTests/Application/ApplicationScreenTests.cs +++ b/UnitTests/Application/ApplicationScreenTests.cs @@ -1,6 +1,6 @@ using Xunit.Abstractions; -namespace Terminal.Gui.ApplicationTests.NavigationTests; +namespace Terminal.Gui.ApplicationTests; public class ApplicationScreenTests (ITestOutputHelper output) { @@ -22,10 +22,10 @@ public class ApplicationScreenTests (ITestOutputHelper output) } [Fact] - [SetupFakeDriver] public void ClearContents_Called_When_Top_Frame_Changes () { // Arrange + Application.Init (new FakeDriver ()); Application.Top = new Toplevel (); Application.TopLevels.Push (Application.Top); @@ -61,6 +61,8 @@ public class ApplicationScreenTests (ITestOutputHelper output) Assert.Equal (3, clearedContentsRaised); // Cleanup - Application.ResetState (true); + Application.Top.Dispose (); + Application.Top = null; + Application.Shutdown (); } } diff --git a/UnitTests/Configuration/ConfigPropertyTests.cs b/UnitTests/Configuration/ConfigPropertyTests.cs index 0bf96dc6e..f751b3d72 100644 --- a/UnitTests/Configuration/ConfigPropertyTests.cs +++ b/UnitTests/Configuration/ConfigPropertyTests.cs @@ -5,6 +5,8 @@ using System.Text.Json.Serialization; using Terminal.Gui; using Xunit; +namespace Terminal.Gui.ConfigurationTests; + public class ConfigPropertyTests { [Fact] diff --git a/UnitTests/Drawing/SixelEncoderTests.cs b/UnitTests/Drawing/SixelEncoderTests.cs index 65d9e423a..f85942e72 100644 --- a/UnitTests/Drawing/SixelEncoderTests.cs +++ b/UnitTests/Drawing/SixelEncoderTests.cs @@ -1,6 +1,6 @@ using Color = Terminal.Gui.Color; -namespace UnitTests.Drawing; +namespace Terminal.Gui.DrawingTests; public class SixelEncoderTests { diff --git a/UnitTests/LocalPackagesTests.cs b/UnitTests/LocalPackagesTests.cs index 5de7b371f..04e5c7802 100644 --- a/UnitTests/LocalPackagesTests.cs +++ b/UnitTests/LocalPackagesTests.cs @@ -1,4 +1,5 @@ -namespace Terminal.Gui; + +namespace Terminal.Gui.BuildAndDeployTests; public class LocalPackagesTests {