Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
63bb2e0
Implement Unix driver
BDisp Sep 15, 2025
819b209
Add more sequences related with macOS
BDisp Sep 15, 2025
5178eda
Helper method to map char to control keys
BDisp Sep 15, 2025
b0949c9
Add DriverName property
BDisp Sep 15, 2025
4e7cd02
Fix error on Unix unit tests
BDisp Sep 16, 2025
e170a79
Fix Non-nullable property 'DriverName' must contain a non-null value …
BDisp Sep 16, 2025
5be7cef
Fix Ctrl being ignored in the range \u0001-\u001a
BDisp Sep 16, 2025
759462e
Add unit test for the MapChar method
BDisp Sep 16, 2025
1fdf1d1
Fix cursor visibility and cursor styles
BDisp Sep 16, 2025
e6c07d2
Avoid sometimes error when running gnome-terminal
BDisp Sep 21, 2025
329422b
Captures Ctrl+Shift+Alt+D7
BDisp Sep 21, 2025
f800a89
Captures Ctrl+D4, Ctrl+D5, Ctrl+D6 and Ctrl+D7
BDisp Sep 21, 2025
fefc240
Add unit test for Ctrl+Shift+Alt+7
BDisp Sep 21, 2025
ae8d765
Fix issue on Windows where sending AltGr+some key fail assertion
BDisp Sep 18, 2025
be52c37
Exclude Oem1 from assertion
BDisp Sep 21, 2025
57f3d46
Fix regex pattern
BDisp Sep 22, 2025
004e9f9
Remove driverName from the constructor
BDisp Sep 22, 2025
9d1807e
Revert "Remove driverName from the constructor"
BDisp Sep 22, 2025
f3d55b8
Remove driverName from the constructor
BDisp Sep 22, 2025
167d65c
Add generic CreateSubcomponents method avoiding redundancy
BDisp Sep 22, 2025
893d926
Add v2unix profiles
BDisp Sep 22, 2025
4d2443e
Replace with hexadecimal values
BDisp Sep 22, 2025
91e6b7a
Merge branch 'v2_develop' into v2_4250_v2unix-new
tig Oct 2, 2025
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
34 changes: 32 additions & 2 deletions Examples/UICatalog/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
"commandLineArgs": "dotnet UICatalog.dll --driver v2",
"distributionName": ""
},
"WSL: UICatalog --driver v2unix": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet UICatalog.dll --driver v2unix",
"distributionName": ""
},
"WSL: UICatalog --driver v2net": {
"commandName": "Executable",
"executablePath": "wsl",
Expand All @@ -63,13 +69,19 @@
"WSL-Gnome: UICatalog --driver v2": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'",
"commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'",
"distributionName": ""
},
"WSL-Gnome: UICatalog --driver v2unix": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2unix; exec bash\"'",
"distributionName": ""
},
"WSL-Gnome: UICatalog --driver v2net": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'",
"commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'",
"distributionName": ""
},
"Benchmark All": {
Expand All @@ -94,6 +106,24 @@
"commandLineArgs": "dotnet UICatalog.dll --benchmark",
"distributionName": ""
},
"WSL: Benchmark All --driver v2": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet UICatalog.dll --driver v2 --benchmark",
"distributionName": ""
},
"WSL: Benchmark All --driver v2unix": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet UICatalog.dll --driver v2unix --benchmark",
"distributionName": ""
},
"WSL: Benchmark All --driver v2net": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet UICatalog.dll --driver v2net --benchmark",
"distributionName": ""
},
"Docker": {
"commandName": "Docker"
},
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/App/Application.Initialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public static (List<Type?>, List<string?>) GetDriverTypes ()
List<string?> driverTypeNames = driverTypes
.Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
.Select (d => d!.Name)
.Union (["v2", "v2win", "v2net"])
.Union (["v2", "v2win", "v2net", "v2unix"])
.ToList ()!;

return (driverTypes, driverTypeNames);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class EscAsAltPattern : AnsiKeyboardParserPattern
public EscAsAltPattern () { IsLastMinute = true; }

#pragma warning disable IDE1006 // Naming Styles
private static readonly Regex _pattern = new (@"^\u001b([a-zA-Z0-9_])$");
private static readonly Regex _pattern = new (@"^\u001b([\u0001-\u001a\u001fa-zA-Z0-9_])$");
#pragma warning restore IDE1006 // Naming Styles

public override bool IsMatch (string? input) { return _pattern.IsMatch (input!); }
Expand All @@ -22,7 +22,14 @@ internal class EscAsAltPattern : AnsiKeyboardParserPattern
return null;
}

char key = match.Groups [1].Value [0];
char ch = match.Groups [1].Value [0];

Key key = ch switch
{
>= '\u0001' and <= '\u001a' => ((Key)(ch + 96)).WithCtrl,
'\u001f' => Key.D7.WithCtrl.WithShift,
_ => ch
};

return new Key (key).WithAlt;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Terminal.Gui.Drivers;
public class Ss3Pattern : AnsiKeyboardParserPattern
{
#pragma warning disable IDE1006 // Naming Styles
private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCAB])$");
private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCABOHFwqysu])$");
#pragma warning restore IDE1006 // Naming Styles

/// <inheritdoc/>
Expand Down Expand Up @@ -41,6 +41,13 @@ public class Ss3Pattern : AnsiKeyboardParserPattern
'C' => Key.CursorRight,
'A' => Key.CursorUp,
'B' => Key.CursorDown,
'H' => Key.Home,
'F' => Key.End,
'w' => Key.Home,
'q' => Key.End,
'y' => Key.PageUp,
's' => Key.PageDown,
'u' => Key.Clear,
_ => null
};
}
Expand Down
54 changes: 52 additions & 2 deletions Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,16 @@ Action<MouseFlags, Point> continuousButtonPressedHandler
//}
}

/// <summary>
/// Helper to set the Control key states based on the char.
/// </summary>
/// <param name="ch">The char value.</param>
/// <returns></returns>
public static ConsoleKeyInfo MapChar (char ch)
{
return MapConsoleKeyInfo (new (ch, ConsoleKey.None, false, false, false));
}

/// <summary>
/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
/// </summary>
Expand Down Expand Up @@ -1131,6 +1141,17 @@ public static ConsoleKeyInfo MapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
true);
}

break;
case uint n when n is >= '\u001c' and <= '\u001f':
key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + 24);

newConsoleKeyInfo = new (
(char)key,
key,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
true);

break;
case 127: // DEL
key = ConsoleKey.Backspace;
Expand Down Expand Up @@ -1375,6 +1396,12 @@ internal static KeyCode MapKey (ConsoleKeyInfo keyInfo)
{
switch (keyInfo.Key)
{
case ConsoleKey.Multiply:
case ConsoleKey.Add:
case ConsoleKey.Separator:
case ConsoleKey.Subtract:
case ConsoleKey.Decimal:
case ConsoleKey.Divide:
case ConsoleKey.OemPeriod:
case ConsoleKey.OemComma:
case ConsoleKey.OemPlus:
Expand All @@ -1391,8 +1418,31 @@ internal static KeyCode MapKey (ConsoleKeyInfo keyInfo)
case ConsoleKey.Oem102:
if (keyInfo.KeyChar == 0)
{
// If the keyChar is 0, keyInfo.Key value is not a printable character.
System.Diagnostics.Debug.Assert (keyInfo.Key == 0);
// All Oem* produce a valid KeyChar and is not guaranteed to be printable ASCII, but it’s never just '\0' (null).
// If that happens it's because Console.ReadKey is misreporting for AltGr + non-character keys
// or if it's a combine key waiting for the next input which will determine the respective KeyChar.
// This behavior only happens on Windows and not on Unix-like systems.
if (keyInfo.Key != ConsoleKey.Multiply
&& keyInfo.Key != ConsoleKey.Add
&& keyInfo.Key != ConsoleKey.Decimal
&& keyInfo.Key != ConsoleKey.Subtract
&& keyInfo.Key != ConsoleKey.Divide
&& keyInfo.Key != ConsoleKey.OemPeriod
&& keyInfo.Key != ConsoleKey.OemComma
&& keyInfo.Key != ConsoleKey.OemPlus
&& keyInfo.Key != ConsoleKey.OemMinus
&& keyInfo.Key != ConsoleKey.Oem1
&& keyInfo.Key != ConsoleKey.Oem2
&& keyInfo.Key != ConsoleKey.Oem3
&& keyInfo.Key != ConsoleKey.Oem4
&& keyInfo.Key != ConsoleKey.Oem5
&& keyInfo.Key != ConsoleKey.Oem6
&& keyInfo.Key != ConsoleKey.Oem7
&& keyInfo.Key != ConsoleKey.Oem102)
{
// 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);
}
Expand Down
59 changes: 19 additions & 40 deletions Terminal.Gui/Drivers/V2/ApplicationV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,29 @@ private void CreateDriver (string? driverName)
{
PlatformID p = Environment.OSVersion.Platform;

bool definetlyWin = (driverName?.Contains ("win") ?? false )|| _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
bool definetlyNet = (driverName?.Contains ("net") ?? false ) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
bool definetlyWin = (driverName?.Contains ("win") ?? false) || _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
bool definetlyNet = (driverName?.Contains ("net") ?? false) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
bool definetlyUnix = (driverName?.Contains ("unix") ?? false) || _componentFactory is IComponentFactory<char>;

if (definetlyWin)
{
_coordinator = CreateWindowsSubcomponents ();
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
}
else if (definetlyNet)
{
_coordinator = CreateNetSubcomponents ();
_coordinator = CreateSubcomponents (() => new NetComponentFactory ());
}
else if (definetlyUnix)
{
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
}
else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
{
_coordinator = CreateWindowsSubcomponents ();
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
}
else
{
_coordinator = CreateNetSubcomponents ();
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
}

_coordinator.StartAsync ().Wait ();
Expand All @@ -109,49 +114,23 @@ private void CreateDriver (string? driverName)
}
}

private IMainLoopCoordinator CreateWindowsSubcomponents ()
{
ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ();
MainLoop<WindowsConsole.InputRecord> loop = new ();

IComponentFactory<WindowsConsole.InputRecord> cf;

if (_componentFactory != null)
{
cf = (IComponentFactory<WindowsConsole.InputRecord>)_componentFactory;
}
else
{
cf = new WindowsComponentFactory ();
}

return new MainLoopCoordinator<WindowsConsole.InputRecord> (_timedEvents,
inputBuffer,
loop,
cf);
}

private IMainLoopCoordinator CreateNetSubcomponents ()
private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
{
ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ();
MainLoop<ConsoleKeyInfo> loop = new ();
ConcurrentQueue<T> inputBuffer = new ();
MainLoop<T> loop = new ();

IComponentFactory<ConsoleKeyInfo> cf;
IComponentFactory<T> cf;

if (_componentFactory != null)
if (_componentFactory is IComponentFactory<T> typedFactory)
{
cf = (IComponentFactory<ConsoleKeyInfo>)_componentFactory;
cf = typedFactory;
}
else
{
cf = new NetComponentFactory ();
cf = fallbackFactory ();
}

return new MainLoopCoordinator<ConsoleKeyInfo> (
_timedEvents,
inputBuffer,
loop,
cf);
return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
}

/// <inheritdoc/>
Expand Down
11 changes: 1 addition & 10 deletions Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,7 @@ public void ClearContents ()
/// <inheritdoc/>
public virtual string GetVersionInfo ()
{
var type = "";

if (InputProcessor is WindowsInputProcessor)
{
type = "win";
}
else if (InputProcessor is NetInputProcessor)
{
type = "net";
}
string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));

return "v2" + type;
}
Expand Down
5 changes: 5 additions & 0 deletions Terminal.Gui/Drivers/V2/IInputProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public interface IInputProcessor
/// <summary>Event fired when a mouse event occurs.</summary>
event EventHandler<MouseEventArgs>? MouseEvent;

/// <summary>
/// Gets the name of the driver associated with this input processor.
/// </summary>
string DriverName { get; init; }

/// <summary>
/// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
/// <see cref="OnKeyUp"/>.
Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Drivers/V2/IUnixInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Terminal.Gui.Drivers;

internal interface IUnixInput : IConsoleInput<char>;
3 changes: 3 additions & 0 deletions Terminal.Gui/Drivers/V2/InputProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
/// </summary>
public ConcurrentQueue<T> InputBuffer { get; }

/// <inheritdoc />
public string DriverName { get; init; }

/// <inheritdoc/>
public IAnsiResponseParser GetParser () { return Parser; }

Expand Down Expand Up @@ -96,7 +99,7 @@
/// Key converter for translating driver specific
/// <typeparamref name="T"/> class into Terminal.Gui <see cref="Key"/>.
/// </param>
protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyConverter)

Check warning on line 102 in Terminal.Gui/Drivers/V2/InputProcessor.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

Non-nullable property 'DriverName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 102 in Terminal.Gui/Drivers/V2/InputProcessor.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Non-nullable property 'DriverName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
InputBuffer = inputBuffer;
Parser.HandleMouse = true;
Expand Down
5 changes: 4 additions & 1 deletion Terminal.Gui/Drivers/V2/NetInputProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
#pragma warning restore CA2211

/// <inheritdoc/>
public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ()) { }
public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
{
DriverName = "net";
}

/// <inheritdoc/>
protected override void Process (ConsoleKeyInfo consoleKeyInfo)
Expand Down
29 changes: 29 additions & 0 deletions Terminal.Gui/Drivers/V2/UnixComponentFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#nullable enable
using System.Collections.Concurrent;

namespace Terminal.Gui.Drivers;

/// <summary>
/// <see cref="IComponentFactory{T}"/> implementation for native unix console I/O i.e. v2unix.
/// This factory creates instances of internal classes <see cref="UnixInput"/>, <see cref="UnixOutput"/> etc.
/// </summary>
public class UnixComponentFactory : ComponentFactory<char>
{
/// <inheritdoc />
public override IConsoleInput<char> CreateInput ()
{
return new UnixInput ();
}

/// <inheritdoc />
public override IInputProcessor CreateInputProcessor (ConcurrentQueue<char> inputBuffer)
{
return new UnixInputProcessor (inputBuffer);
}

/// <inheritdoc />
public override IConsoleOutput CreateOutput ()
{
return new UnixOutput ();
}
}
Loading
Loading