Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sixel encoder is supported #169

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4650522
Fixes #3767. Allowing any driver to request ANSI escape sequence with…
BDisp Sep 30, 2024
c392ae6
Using a more appropriate request for cursor position.
BDisp Sep 30, 2024
05fafca
Add AnsiEscapeSequenceRequest and AnsiEscapeSequenceResponse classes.
BDisp Sep 30, 2024
3aa4e61
Prevents empty response error.
BDisp Oct 1, 2024
9297172
Add AnsiEscapeSequenceRequests scenario.
BDisp Oct 1, 2024
6a6c210
Fixes #3771. TextView doesn't consider no-printable rune in draw and …
BDisp Oct 1, 2024
7bf6aaa
Add unit test which already works in TextField.
BDisp Oct 1, 2024
e4d30a1
Replace Frame to Viewport in TextField.
BDisp Oct 1, 2024
ba6ab41
Merge branch 'v2_3771_textview-no-printable-rune-fix' into v2_3767_an…
BDisp Oct 1, 2024
5b2dd47
Improving scenario layout.
BDisp Oct 1, 2024
2470aec
Fix NetDriver read key issue.
BDisp Oct 2, 2024
c44715a
Change file name.
BDisp Oct 2, 2024
026a20e
Fixes #3774. TextModel.ToRuneCellList is internal and is better move …
BDisp Oct 2, 2024
41e446f
Merge branch 'v2_3771_textview-no-printable-rune-fix' into v2_3767_an…
BDisp Oct 2, 2024
927a48c
Improves null dequeues handling.
BDisp Oct 2, 2024
620e3e4
Merge branch 'v2_develop' into v2_3767_ansi-escape-sequence-all-drivers
BDisp Oct 2, 2024
2292a86
Merge branch 'v2_develop' into v2_3771_textview-no-printable-rune-fix
BDisp Oct 2, 2024
153a9f2
Trying fix unit test error.
BDisp Oct 2, 2024
7579a46
Merge branch 'v2_3771_textview-no-printable-rune-fix' into v2_3767_an…
BDisp Oct 2, 2024
fed1827
Merge #3768 into sixel branch
tznind Oct 5, 2024
3f4eac9
Change to using ansi escape sequences in new standalone class
tznind Oct 5, 2024
ef5eabe
Fix algorithm to look for exactly 4 not things like 42 etc.
tznind Oct 5, 2024
64a15a8
Fix new palette order
tznind Oct 5, 2024
dfedcd8
Replace ExecuteAnsiRequest with TryParse.
BDisp Oct 5, 2024
f9ff927
Code cleanup.
BDisp Oct 5, 2024
31dbae7
Replace from TryParse to TryExecuteAnsiRequest.
BDisp Oct 5, 2024
cea2555
Merge branch 'sixel-encoder-tinkering' into sixel-encoder-is-supported
tznind Oct 6, 2024
4672d52
Merge branch 'v2_3767_ansi-escape-sequence-all-drivers' into sixel-en…
tznind Oct 6, 2024
02e2627
Get sixel resolution using CSI 16 t
tznind Oct 6, 2024
eb6f6dc
Sixel resolution measuring
tznind Oct 6, 2024
76223eb
Update sixel status class name and move to new file
tznind Oct 6, 2024
7d2c184
Update xmldoc
tznind Oct 6, 2024
ba2a803
Use round to nearest whole number when calculating sixel resolution f…
tznind Oct 7, 2024
20c2270
Refactor SixelSupportDetector for cleaner reading
tznind Oct 7, 2024
1f46d93
If not supporting transparency warn user
tznind Oct 7, 2024
a5e4a00
Merge branch 'v2_develop' into v2_3767_ansi-escape-sequence-all-drivers
BDisp Oct 7, 2024
14a89c3
Fix exception throwing if no terminator is specified.
BDisp Oct 7, 2024
7d11812
Merge branch 'v2_3767_ansi-escape-sequence-all-drivers' into sixel-en…
tznind Oct 9, 2024
693edd6
Merge branch 'sixel-encoder-tinkering' into sixel-encoder-is-supported
tznind Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#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>
/// 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>
/// Execute an ANSI escape sequence escape which may return a response or error.
/// </summary>
/// <param name="ansiRequest">The ANSI escape sequence to request.</param>
/// <param name="result">
/// When this method returns <see langword="true"/>, an object containing the response with an empty
/// error.
/// </param>
/// <returns>A <see cref="AnsiEscapeSequenceResponse"/> with the response, error, terminator and value.</returns>
public static bool TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result)
{
var response = new StringBuilder ();
var error = new StringBuilder ();
var savedIsReportingMouseMoves = false;
NetDriver? netDriver = null;
var values = new string? [] { 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);

// 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 (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]}'");
}
}
catch (Exception ex)
{
error.AppendLine ($"Error executing ANSI request: {ex.Message}");
}
finally
{
if (string.IsNullOrEmpty (error.ToString ()))
{
(string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ());
}

if (savedIsReportingMouseMoves)
{
switch (Application.Driver)
{
case NetDriver:
NetEvents._suspendRead = false;
netDriver!.StartReportingMouseMoves ();

break;
case CursesDriver cursesDriver:
cursesDriver.StartReportingMouseMoves ();

break;
}
}
}

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

result = ansiResponse;

return string.IsNullOrWhiteSpace (result.Error) && !string.IsNullOrWhiteSpace (result.Response);
}

/// <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
34 changes: 14 additions & 20 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,35 @@ 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/
/// CSI 16 t - Request sixel resolution (width and height in pixels)
/// </summary>
public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t";
public static readonly AnsiEscapeSequenceRequest CSI_RequestSixelResolution = new () { Request = CSI + "16t", Terminator = "t" };

/// <summary>
/// The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t
/// CSI 14 t - Request window size in pixels (width x height)
/// </summary>
public const string CSI_ReportTerminalSizeInChars_Terminator = "t";
public static readonly AnsiEscapeSequenceRequest CSI_RequestWindowSizeInPixels = new () { Request = CSI + "14t", Terminator = "t" };

/// <summary>
/// The value of the response to <see cref="CSI_ReportTerminalSizeInChars"/> indicating value 1 and 2 are the terminal
/// size in chars.
/// CSI 1 8 t | yes | yes | yes | report window size in chars
/// https://terminalguide.namepad.de/seq/csi_st-18/
/// The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t
/// </summary>
public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" };

#endregion
}
Loading