Skip to content

Commit

Permalink
Merge gui-cs#3768 into sixel branch
Browse files Browse the repository at this point in the history
This work by @BDisp enables us to detect sixel support on demand
  • Loading branch information
tznind committed Oct 5, 2024
1 parent 97da4cd commit fed1827
Show file tree
Hide file tree
Showing 13 changed files with 504 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#nullable enable
namespace Terminal.Gui;

/// <summary>
/// Describes an ongoing ANSI request sent to the console.
/// Use <see cref="ResponseReceived"/> to handle the response
/// when console answers the request.
/// </summary>
public class AnsiEscapeSequenceRequest
{
/// <summary>
/// Execute an ANSI escape sequence escape which may return a response or error.
/// </summary>
/// <param name="ansiRequest">The ANSI escape sequence to request.</param>
/// <returns>A <see cref="AnsiEscapeSequenceResponse"/> with the response, error, terminator and value.</returns>
public static AnsiEscapeSequenceResponse ExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
{
var response = new StringBuilder ();
var error = new StringBuilder ();
var savedIsReportingMouseMoves = false;
NetDriver? netDriver = null;

try
{
switch (Application.Driver)
{
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;
}

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);
}
}

// 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:
NetEvents._suspendRead = false;
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 = string.IsNullOrEmpty (response.ToString ()) ? "" : response.ToString () [^1].ToString (), Value = values [0]
};

// Invoke the event if it's subscribed
ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse);

return ansiResponse;
}

/// <summary>
/// Request to send e.g. see
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
/// </see>
/// </summary>
public required string Request { get; init; }

/// <summary>
/// Invoked when the console responds with an ANSI response code that matches the
/// <see cref="Terminator"/>
/// </summary>
public event EventHandler<AnsiEscapeSequenceResponse>? ResponseReceived;

/// <summary>
/// <para>
/// The terminator that uniquely identifies the type of response as responded
/// by the console. e.g. for
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
/// </see>
/// the terminator is
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Terminator</cref>
/// </see>
/// .
/// </para>
/// <para>
/// After sending a request, the first response with matching terminator will be matched
/// to the oldest outstanding request.
/// </para>
/// </summary>
public required string Terminator { get; init; }

/// <summary>
/// The value expected in the response e.g.
/// <see>
/// <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
/// </see>
/// which will have a 't' as terminator but also other different request may return the same terminator with a
/// different value.
/// </summary>
public string? Value { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#nullable enable
namespace Terminal.Gui;

/// <summary>
/// Describes a finished ANSI received from the console.
/// </summary>
public class AnsiEscapeSequenceResponse
{
/// <summary>
/// Error received from e.g. see
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
/// </see>
/// </summary>
public required string Error { get; init; }

/// <summary>
/// Response received from e.g. see
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
/// </see>
/// .
/// </summary>
public required string Response { get; init; }

/// <summary>
/// <para>
/// The terminator that uniquely identifies the type of response as responded
/// by the console. e.g. for
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
/// </see>
/// the terminator is
/// <see>
/// <cref>EscSeqUtils.CSI_SendDeviceAttributes.Terminator</cref>
/// </see>
/// </para>
/// <para>
/// The received terminator must match to the terminator sent by the request.
/// </para>
/// </summary>
public required string Terminator { get; init; }

/// <summary>
/// The value expected in the response e.g.
/// <see>
/// <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
/// </see>
/// which will have a 't' as terminator but also other different request may return the same terminator with a
/// different value.
/// </summary>
public string? Value { get; init; }
}
6 changes: 6 additions & 0 deletions Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,15 @@ public override bool SetCursorVisibility (CursorVisibility visibility)
return true;
}

public bool IsReportingMouseMoves { get; private set; }

public void StartReportingMouseMoves ()
{
if (!RunningUnitTests)
{
Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);

IsReportingMouseMoves = true;
}
}

Expand All @@ -190,6 +194,8 @@ public void StopReportingMouseMoves ()
if (!RunningUnitTests)
{
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);

IsReportingMouseMoves = false;
}
}

Expand Down
30 changes: 7 additions & 23 deletions Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1316,13 +1316,9 @@ public enum DECSCUSR_Style
/// <summary>
/// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR)
/// https://terminalguide.namepad.de/seq/csi_sn__p-6/
/// The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) ; 1 R
/// </summary>
public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n";

/// <summary>
/// The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) R
/// </summary>
public const string CSI_RequestCursorPositionReport_Terminator = "R";
public static readonly AnsiEscapeSequenceRequest CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" };

/// <summary>
/// ESC [ 0 c - Send Device Attributes (Primary DA)
Expand All @@ -1341,37 +1337,25 @@ public enum DECSCUSR_Style
/// 28 = Rectangular area operations
/// 32 = Text macros
/// 42 = ISO Latin-2 character set
/// The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
/// <see cref="CSI_SendDeviceAttributes2"/>
/// </summary>
public static readonly string CSI_SendDeviceAttributes = CSI + "0c";
public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" };

/// <summary>
/// ESC [ > 0 c - Send Device Attributes (Secondary DA)
/// Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220)
/// </summary>
public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c";

/// <summary>
/// The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
/// <see cref="CSI_SendDeviceAttributes2"/>
/// </summary>
public const string CSI_ReportDeviceAttributes_Terminator = "c";
public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" };

/// <summary>
/// CSI 1 8 t | yes | yes | yes | report window size in chars
/// https://terminalguide.namepad.de/seq/csi_st-18/
/// </summary>
public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t";

/// <summary>
/// The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t
/// </summary>
public const string CSI_ReportTerminalSizeInChars_Terminator = "t";

/// <summary>
/// The value of the response to <see cref="CSI_ReportTerminalSizeInChars"/> indicating value 1 and 2 are the terminal
/// size in chars.
/// </summary>
public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" };

#endregion
}
Loading

0 comments on commit fed1827

Please sign in to comment.