Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e7f4169
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 5, 2026
d4bdf24
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 7, 2026
9f8d51e
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 7, 2026
945094b
Update kitty keyboard support plan
tig Mar 7, 2026
32acf8c
Merge branch 'v2_develop' into feature/kitty
tig Mar 7, 2026
e691925
Merge branch 'feature/kitty' of tig:tig/Terminal.Gui into feature/kitty
tig Mar 7, 2026
c1ae836
Add kitty keyboard protocol phase 1 support
tig Mar 7, 2026
e7e35a7
Update kitty phase 1 progress docs
tig Mar 8, 2026
3b3fb12
Finish kitty keyboard protocol phase 1
tig Mar 8, 2026
2e10e13
Centralize terminal detection in Driver.IsAttachedToTerminal
tig Mar 8, 2026
a3b69a0
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 8, 2026
8b7bd67
Set DisableRealDriverIO for all GitHub test jobs
tig Mar 8, 2026
06713b6
Refactor drivers: cleanup, modern C#, bug fixes
tig Mar 8, 2026
378d462
Gracefully handle no-terminal scenarios in output drivers
tig Mar 8, 2026
3c59dd3
Refactor IsAttachedToTerminal checks in Output classes
tig Mar 8, 2026
35d5ad9
Refactor terminal detection and modernize input tests
tig Mar 8, 2026
92b183f
Centralize terminal detection in Driver.IsAttachedToTerminal
tig Mar 8, 2026
6181bcf
Set DisableRealDriverIO for all GitHub test jobs
tig Mar 8, 2026
f7c6cdb
Refactor drivers: cleanup, modern C#, bug fixes
tig Mar 8, 2026
90a48d2
Gracefully handle no-terminal scenarios in output drivers
tig Mar 8, 2026
d4f1cd7
Refactor IsAttachedToTerminal checks in Output classes
tig Mar 8, 2026
e7b4d28
Refactor terminal detection and modernize input tests
tig Mar 8, 2026
efa771e
Remove kitty keyboard code from terminal detection PR
tig Mar 8, 2026
63e51c4
deleted empty test file
tig Mar 8, 2026
6cf98ac
code cleanup
tig Mar 8, 2026
046af97
Add env var and terminal tests to IntegrationTests suite
tig Mar 8, 2026
7abd734
Merge branch 'fix/4804-terminal-detection' into pr-4810
tig Mar 8, 2026
ecca89f
Update Terminal.Gui/Drivers/Output/OutputBase.cs
tig Mar 8, 2026
942318a
Update Terminal.Gui/Drivers/DotNetDriver/NetInput.cs
tig Mar 8, 2026
a3e595f
Update Tests/IntegrationTests/FluentTests/TestContextTests.cs
tig Mar 8, 2026
4cbdc09
Update Terminal.Gui/Drivers/Input/InputImpl.cs
tig Mar 8, 2026
902c2b3
Refactor tests and AnsiOutput; improve logging setup
tig Mar 8, 2026
8720f37
Merge remote-tracking branch 'upstream/fix/4804-terminal-detection' i…
tig Mar 8, 2026
ced7715
Update Terminal.Gui/Drivers/Output/OutputBase.cs
tig Mar 8, 2026
8fef93d
Refactor MainLoopCoordinatorTests to use TestLogging instead of globa…
tig Mar 8, 2026
0aa33a6
Merge branch 'fix/4804-terminal-detection' into pr-4810
tig Mar 8, 2026
448223c
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 9, 2026
76aa1be
Standardize tracing and simplify input/output logic
tig Mar 9, 2026
d2cb931
Clean up logging, tracing, and property usage
tig Mar 9, 2026
f1ecbb8
Merge branch 'v2_develop' into feature/kitty
tig Mar 9, 2026
7fb84bd
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 9, 2026
40fa28b
Merge branch 'v2_develop' into feature/kitty
tig Mar 9, 2026
78b2303
Merge branch 'feature/kitty' of tig:tig/Terminal.Gui into feature/kitty
tig Mar 9, 2026
6021b3f
Removed setting of global var
tig Mar 9, 2026
cb3e033
Handle degraded mode in kitty keyboard enablement
tig Mar 9, 2026
2df45a9
removed old doc
tig Mar 9, 2026
6ca7ab4
Merge branch 'gui-cs:v2_develop' into v2_develop
tig Mar 9, 2026
6ad87e1
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Mar 9, 2026
47a54aa
Merge branch 'v2_develop' into feature/kitty
tig Mar 9, 2026
fdf967c
Update kitty keyboard plan with Phase 2 implementation steps
tig Mar 9, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
timeout-minutes: 15
env:
DisableRealDriverIO: "1"

steps:
- name: Checkout code
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/stress-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
os: [ ubuntu-latest ]

timeout-minutes: 70 # Allow some buffer time beyond the 1-hour test duration
env:
DisableRealDriverIO: "1"
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down Expand Up @@ -58,6 +60,8 @@ jobs:
os: [ ubuntu-latest, windows-latest, macos-latest ]

timeout-minutes: 90
env:
DisableRealDriverIO: "1"
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
os: [ ubuntu-latest, windows-latest, macos-latest ]

timeout-minutes: 15
env:
DisableRealDriverIO: "1"
steps:

- name: Checkout code
Expand Down Expand Up @@ -76,6 +78,8 @@ jobs:
os: [ ubuntu-latest, windows-latest, macos-latest ]

timeout-minutes: 60
env:
DisableRealDriverIO: "1"
steps:

- name: Checkout code
Expand Down
28 changes: 28 additions & 0 deletions Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,34 @@ private void BuildDriverIfPossible (IApplication? app)
}
}

try
{
KittyKeyboardProtocolDetector kittyKeyboardDetector = new (_driver);
kittyKeyboardDetector.Detect (result =>
{
_driver.SetKittyKeyboardProtocol (result);
Trace.Lifecycle (app?.MainThreadId?.ToString (),
"KittyKeyboard",
$"Probe complete: Supported={result.IsSupported}, SupportedFlags={result.SupportedFlags}, EnabledFlags={result.EnabledFlags}");

if (!result.IsSupported || result.EnabledFlags <= 0 || _output is not AnsiOutput ansiOutput)
{
Trace.Lifecycle (app?.MainThreadId?.ToString (), "KittyKeyboard", "Kitty keyboard mode not enabled");
return;
}

ansiOutput.EnableKittyKeyboard (result.EnabledFlags);
_driver.SetKittyKeyboardEnabledFlags (ansiOutput.KittyKeyboardEnabledFlags);
Trace.Lifecycle (app?.MainThreadId?.ToString (),
"KittyKeyboard",
$"Enabled kitty keyboard flags {ansiOutput.KittyKeyboardEnabledFlags}");
});
}
catch (Exception ex)
{
Logging.Warning ($"Kitty keyboard protocol detection failed: {ex.Message}");
}

_startupSemaphore.Release ();
Logging.Trace ($"app: {app.MainThreadId} Driver: _input: {_input}, _output: {_output}");
}
Expand Down
20 changes: 14 additions & 6 deletions Terminal.Gui/Drivers/AnsiDriver/AnsiInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ public AnsiInput ()
try
{
// Check if we have a real console first
if (!AnsiTerminalHelper.IsAttachedToTerminal (out bool inputAttached, out bool outputAttached))
if (!IsAttachedToTerminal)
{
Trace.Lifecycle (nameof (AnsiInput), "Init", $"Console redirected (Output: {outputAttached}, Input: {inputAttached}). Running in degraded mode.");
Trace.Lifecycle (nameof (AnsiInput), "Init", $"Console is not attached to a terminal. Running in degraded mode.");

return;
}
Expand All @@ -105,7 +105,9 @@ public AnsiInput ()
_windowsVTInput.Dispose ();
_windowsVTInput = null;

Trace.Lifecycle (nameof (AnsiInput), "Init", "Failed to enable Windows VT Input mode. Terminal input will not work. Running in degraded mode.");
Trace.Lifecycle (nameof (AnsiInput),
"Init",
"Failed to enable Windows VT Input mode. Terminal input will not work. Running in degraded mode.");

return;
}
Expand All @@ -121,7 +123,9 @@ public AnsiInput ()

if (!_unixRawMode.TryEnable ())
{
Trace.Lifecycle (nameof (AnsiInput), "Init", "Failed to enable Unix raw input mode. Terminal input will not work. Running in degraded mode.");
Trace.Lifecycle (nameof (AnsiInput),
"Init",
"Failed to enable Unix raw input mode. Terminal input will not work. Running in degraded mode.");
_pollMap = null;
_unixRawMode?.Dispose ();
_unixRawMode = null;
Expand All @@ -132,7 +136,9 @@ public AnsiInput ()
}
catch (DllNotFoundException ex)
{
Trace.Lifecycle (nameof (AnsiInput), "Init", $"Failed to enable Unix raw input mode. libc not available: {ex.Message}. Running in degraded mode.");
Trace.Lifecycle (nameof (AnsiInput),
"Init",
$"Failed to enable Unix raw input mode. libc not available: {ex.Message}. Running in degraded mode.");
}
}
else
Expand All @@ -147,7 +153,9 @@ public AnsiInput ()
}
catch (Exception ex)
{
Trace.Lifecycle (nameof (AnsiInput), "Init", $"Failed to initialize terminal: {ex.GetType ().Name}: {ex.Message}. Running in degraded mode. Stack trace: {ex.StackTrace}");
Trace.Lifecycle (nameof (AnsiInput),
"Init",
$"Failed to initialize terminal: {ex.GetType ().Name}: {ex.Message}. Running in degraded mode. Stack trace: {ex.StackTrace}");
_platform = AnsiPlatform.Degraded;
}
}
Expand Down
65 changes: 54 additions & 11 deletions Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class AnsiOutput : OutputBase, IOutput

private Size _consoleSize = new (80, 25);
private IOutputBuffer? _lastBuffer;
private int _kittyKeyboardEnabledFlags;

private readonly WindowsVTOutputHelper? _windowsVTOutput;

Expand All @@ -61,9 +62,9 @@ public AnsiOutput ()
try
{
// Check if we have a real console first
if (!AnsiTerminalHelper.IsAttachedToTerminal (out bool inputAttached, out bool outputAttached))
if (!IsAttachedToTerminal)
{
Trace.Lifecycle (nameof (AnsiOutput), "Init", $"Console redirected (Output: {outputAttached}, Input: {inputAttached}). Running in degraded mode.");
Trace.Lifecycle (nameof (AnsiOutput), "Init", "No real terminal attached. Running in degraded mode.");

return;
}
Expand All @@ -78,7 +79,9 @@ public AnsiOutput ()
_windowsVTOutput.Dispose ();
_windowsVTOutput = null;

Trace.Lifecycle (nameof (AnsiOutput), "Init", "Failed to enable Windows VT Input mode. Terminal input will not work. Running in degraded mode.");
Trace.Lifecycle (nameof (AnsiOutput),
"Init",
"Failed to enable Windows VT Input mode. Terminal input will not work. Running in degraded mode.");

return;
}
Expand Down Expand Up @@ -178,14 +181,16 @@ protected override void Write (StringBuilder output)
/// <inheritdoc/>
public void Write (ReadOnlySpan<char> text)
{
StringBuilder capturedOutput = new ();
capturedOutput.Append (text);
base.Write (capturedOutput);

try
{
switch (_platform)
{
case AnsiPlatform.WindowsVT:
StringBuilder sb = new ();
sb.Append (text);
_windowsVTOutput!.Write (sb);
_windowsVTOutput!.Write (capturedOutput);

break;

Expand All @@ -208,6 +213,42 @@ public void Write (ReadOnlySpan<char> text)
}
}

/// <summary>
/// Gets the kitty keyboard flags currently enabled on the terminal.
/// </summary>
internal int KittyKeyboardEnabledFlags => _kittyKeyboardEnabledFlags;

/// <summary>
/// Enables kitty keyboard progressive enhancement flags for the active terminal.
/// </summary>
/// <param name="flags">The kitty keyboard flags to enable.</param>
internal void EnableKittyKeyboard (int flags)
{
if (flags <= 0)
{
return;
}

Trace.Lifecycle (nameof (AnsiOutput), "KittyKeyboard", $"Writing enable sequence for flags {flags}");
Write (EscSeqUtils.CSI_EnableKittyKeyboardFlags (flags));
_kittyKeyboardEnabledFlags = flags;
}

/// <summary>
/// Restores the previous kitty keyboard flag state if kitty mode was enabled.
/// </summary>
internal void DisableKittyKeyboard ()
{
if (_kittyKeyboardEnabledFlags <= 0)
{
return;
}

Trace.Lifecycle (nameof (AnsiOutput), "KittyKeyboard", $"Writing disable sequence for flags {_kittyKeyboardEnabledFlags}");
Write (EscSeqUtils.CSI_DisableKittyKeyboardFlags);
_kittyKeyboardEnabledFlags = 0;
}

/// <inheritdoc cref="IOutput.Write(IOutputBuffer)"/>
public override void Write (IOutputBuffer buffer)
{
Expand Down Expand Up @@ -310,13 +351,15 @@ public void HandleSizeQueryResponse (string? response)
/// <inheritdoc/>
public void Dispose ()
{
if (_platform == AnsiPlatform.Degraded)
{
return;
}

try
{
DisableKittyKeyboard ();

if (_platform == AnsiPlatform.Degraded)
{
return;
}

// Restore terminal state: disable mouse, restore buffer, show cursor
// TODO: Move Input related CSI sequences to AnsiInput
Write (EscSeqUtils.CSI_DisableMouseEvents);
Expand Down
30 changes: 0 additions & 30 deletions Terminal.Gui/Drivers/AnsiDriver/AnsiTerminalHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,6 @@ namespace Terminal.Gui.Drivers;

internal static class AnsiTerminalHelper
{
public static bool IsAttachedToTerminal (out bool inputAttached, out bool outputAttached)
{
inputAttached = outputAttached = false;

if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
{
const int STD_INPUT_HANDLE = -10;
const int STD_OUTPUT_HANDLE = -11;
nint inH = GetStdHandle (STD_INPUT_HANDLE);
nint outH = GetStdHandle (STD_OUTPUT_HANDLE);

inputAttached = inH != nint.Zero && GetConsoleMode (inH, out _);
outputAttached = outH != nint.Zero && GetConsoleMode (outH, out _);

return inputAttached && outputAttached;
}
const int STDIN_FILENO = 0;
const int STDOUT_FILENO = 1;
inputAttached = isatty (STDIN_FILENO) == 1;
outputAttached = isatty (STDOUT_FILENO) == 1;

return inputAttached && outputAttached;
}

public static void FlushNative (AnsiPlatform platform)
{
try
Expand Down Expand Up @@ -85,9 +61,6 @@ private static void FlushWindows ()
}

// Unix
[DllImport ("libc", SetLastError = true)]
private static extern int isatty (int fd);

[DllImport ("libc", SetLastError = true)]
private static extern int tcdrain (int fd);

Expand All @@ -98,9 +71,6 @@ private static void FlushWindows ()
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern nint GetStdHandle (int nStdHandle);

[DllImport ("kernel32.dll")]
private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);

[DllImport ("kernel32.dll", SetLastError = true)]
[return: MarshalAs (UnmanagedType.Bool)]
private static extern bool FlushFileBuffers (nint hFile);
Expand Down
1 change: 1 addition & 0 deletions Terminal.Gui/Drivers/AnsiHandling/AnsiKeyboardParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class AnsiKeyboardParser
private readonly List<AnsiKeyboardParserPattern> _patterns =
[
new Ss3Pattern (),
new KittyKeyboardPattern (),
new CsiKeyPattern (),
new CsiCursorPattern (),
new EscAsAltPattern { IsLastMinute = true }
Expand Down
62 changes: 48 additions & 14 deletions Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Text.RegularExpressions;

namespace Terminal.Gui.Drivers;

/// <summary>
Expand All @@ -26,32 +24,68 @@ public bool Matches (string? cur)
return true;
}

// Remove leading ESC if present to simplify parsing
string s = cur;

if (s.Length > 0 && s [0] == '\x1B')
{
s = s [1..];
}

// Extract the first numeric token after '[' (e.g. "[8;..." -> "8", "[6;..." -> "6")
// This matches typical CSI reply formats used here.
Match m = Regex.Match (s, @"^\[(\d+);");
string? csiToken = TryGetLeadingToken (s, '[', Terminator!);

if (!string.IsNullOrEmpty (csiToken))
{
return TokenMatchesValue (csiToken, Value);
}

string? oscToken = TryGetLeadingToken (s, ']', Terminator!);

if (!string.IsNullOrEmpty (oscToken))
{
return TokenMatchesValue (oscToken, Value);
}

return s.Contains ($"[{Value};", StringComparison.Ordinal)
|| s.Contains ($"]{Value};", StringComparison.Ordinal)
|| s.Contains ($"[{Value}", StringComparison.Ordinal)
|| s.Contains ($"]{Value}", StringComparison.Ordinal);
}

private static string? TryGetLeadingToken (string input, char prefix, string terminator)
{
if (string.IsNullOrEmpty (input) || input [0] != prefix)
{
return null;
}

int startIndex = 1;
int endIndex = input.IndexOfAny ([ ';', terminator [0] ], startIndex);

if (endIndex < 0)
{
endIndex = input.Length;
}

if (m.Success)
if (endIndex <= startIndex)
{
return string.Equals (m.Groups [1].Value, Value, StringComparison.Ordinal);
return null;
}

// Extract the first numeric token after ']' for OSC responses (e.g. "]10;..." -> "10", "]11;..." -> "11")
Match oscMatch = Regex.Match (s, @"^\](\d+);");
return input [startIndex..endIndex];
}

private static bool TokenMatchesValue (string token, string value)
{
if (string.Equals (token, value, StringComparison.Ordinal))
{
return true;
}

if (oscMatch.Success)
if (!char.IsDigit (value [0]))
{
return string.Equals (oscMatch.Groups [1].Value, Value, StringComparison.Ordinal);
return token.StartsWith (value, StringComparison.Ordinal);
}

// Fallback: conservative contains check (rare)
return s.Contains ($"[{Value};", StringComparison.Ordinal) || s.Contains ($"]{Value};", StringComparison.Ordinal);
return false;
}
}
Loading
Loading