mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
This commit is contained in:
@@ -42,6 +42,12 @@
|
|||||||
"commandLineArgs": "dotnet UICatalog.dll --driver v2",
|
"commandLineArgs": "dotnet UICatalog.dll --driver v2",
|
||||||
"distributionName": ""
|
"distributionName": ""
|
||||||
},
|
},
|
||||||
|
"WSL: UICatalog --driver v2unix": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "wsl",
|
||||||
|
"commandLineArgs": "dotnet UICatalog.dll --driver v2unix",
|
||||||
|
"distributionName": ""
|
||||||
|
},
|
||||||
"WSL: UICatalog --driver v2net": {
|
"WSL: UICatalog --driver v2net": {
|
||||||
"commandName": "Executable",
|
"commandName": "Executable",
|
||||||
"executablePath": "wsl",
|
"executablePath": "wsl",
|
||||||
@@ -63,13 +69,19 @@
|
|||||||
"WSL-Gnome: UICatalog --driver v2": {
|
"WSL-Gnome: UICatalog --driver v2": {
|
||||||
"commandName": "Executable",
|
"commandName": "Executable",
|
||||||
"executablePath": "wsl",
|
"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": ""
|
"distributionName": ""
|
||||||
},
|
},
|
||||||
"WSL-Gnome: UICatalog --driver v2net": {
|
"WSL-Gnome: UICatalog --driver v2net": {
|
||||||
"commandName": "Executable",
|
"commandName": "Executable",
|
||||||
"executablePath": "wsl",
|
"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": ""
|
"distributionName": ""
|
||||||
},
|
},
|
||||||
"Benchmark All": {
|
"Benchmark All": {
|
||||||
@@ -94,6 +106,24 @@
|
|||||||
"commandLineArgs": "dotnet UICatalog.dll --benchmark",
|
"commandLineArgs": "dotnet UICatalog.dll --benchmark",
|
||||||
"distributionName": ""
|
"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": {
|
"Docker": {
|
||||||
"commandName": "Docker"
|
"commandName": "Docker"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ public static partial class Application // Initialization (Init/Shutdown)
|
|||||||
List<string?> driverTypeNames = driverTypes
|
List<string?> driverTypeNames = driverTypes
|
||||||
.Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
|
.Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
|
||||||
.Select (d => d!.Name)
|
.Select (d => d!.Name)
|
||||||
.Union (["v2", "v2win", "v2net"])
|
.Union (["v2", "v2win", "v2net", "v2unix"])
|
||||||
.ToList ()!;
|
.ToList ()!;
|
||||||
|
|
||||||
return (driverTypes, driverTypeNames);
|
return (driverTypes, driverTypeNames);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ internal class EscAsAltPattern : AnsiKeyboardParserPattern
|
|||||||
public EscAsAltPattern () { IsLastMinute = true; }
|
public EscAsAltPattern () { IsLastMinute = true; }
|
||||||
|
|
||||||
#pragma warning disable IDE1006 // Naming Styles
|
#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
|
#pragma warning restore IDE1006 // Naming Styles
|
||||||
|
|
||||||
public override bool IsMatch (string? input) { return _pattern.IsMatch (input!); }
|
public override bool IsMatch (string? input) { return _pattern.IsMatch (input!); }
|
||||||
@@ -22,7 +22,14 @@ internal class EscAsAltPattern : AnsiKeyboardParserPattern
|
|||||||
return null;
|
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;
|
return new Key (key).WithAlt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Terminal.Gui.Drivers;
|
|||||||
public class Ss3Pattern : AnsiKeyboardParserPattern
|
public class Ss3Pattern : AnsiKeyboardParserPattern
|
||||||
{
|
{
|
||||||
#pragma warning disable IDE1006 // Naming Styles
|
#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
|
#pragma warning restore IDE1006 // Naming Styles
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -41,6 +41,13 @@ public class Ss3Pattern : AnsiKeyboardParserPattern
|
|||||||
'C' => Key.CursorRight,
|
'C' => Key.CursorRight,
|
||||||
'A' => Key.CursorUp,
|
'A' => Key.CursorUp,
|
||||||
'B' => Key.CursorDown,
|
'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
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1029,6 +1029,16 @@ public static class EscSeqUtils
|
|||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
|
/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1131,6 +1141,17 @@ public static class EscSeqUtils
|
|||||||
true);
|
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;
|
break;
|
||||||
case 127: // DEL
|
case 127: // DEL
|
||||||
key = ConsoleKey.Backspace;
|
key = ConsoleKey.Backspace;
|
||||||
@@ -1375,6 +1396,12 @@ public static class EscSeqUtils
|
|||||||
{
|
{
|
||||||
switch (keyInfo.Key)
|
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.OemPeriod:
|
||||||
case ConsoleKey.OemComma:
|
case ConsoleKey.OemComma:
|
||||||
case ConsoleKey.OemPlus:
|
case ConsoleKey.OemPlus:
|
||||||
@@ -1391,8 +1418,31 @@ public static class EscSeqUtils
|
|||||||
case ConsoleKey.Oem102:
|
case ConsoleKey.Oem102:
|
||||||
if (keyInfo.KeyChar == 0)
|
if (keyInfo.KeyChar == 0)
|
||||||
{
|
{
|
||||||
// If the keyChar is 0, keyInfo.Key value is not a printable character.
|
// All Oem* produce a valid KeyChar and is not guaranteed to be printable ASCII, but it’s never just '\0' (null).
|
||||||
System.Diagnostics.Debug.Assert (keyInfo.Key == 0);
|
// 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);
|
return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
|
||||||
}
|
}
|
||||||
@@ -1411,7 +1461,7 @@ public static class EscSeqUtils
|
|||||||
// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
|
// 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.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
|
||||||
{
|
{
|
||||||
if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I)
|
if (keyInfo is { Modifiers: ConsoleModifiers.Control, Key: ConsoleKey.I })
|
||||||
{
|
{
|
||||||
return KeyCode.Tab;
|
return KeyCode.Tab;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,24 +81,29 @@ public class ApplicationV2 : ApplicationImpl
|
|||||||
{
|
{
|
||||||
PlatformID p = Environment.OSVersion.Platform;
|
PlatformID p = Environment.OSVersion.Platform;
|
||||||
|
|
||||||
bool definetlyWin = (driverName?.Contains ("win") ?? false )|| _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
|
bool definetlyWin = (driverName?.Contains ("win") ?? false) || _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
|
||||||
bool definetlyNet = (driverName?.Contains ("net") ?? false ) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
|
bool definetlyNet = (driverName?.Contains ("net") ?? false) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
|
||||||
|
bool definetlyUnix = (driverName?.Contains ("unix") ?? false) || _componentFactory is IComponentFactory<char>;
|
||||||
|
|
||||||
if (definetlyWin)
|
if (definetlyWin)
|
||||||
{
|
{
|
||||||
_coordinator = CreateWindowsSubcomponents ();
|
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
|
||||||
}
|
}
|
||||||
else if (definetlyNet)
|
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)
|
else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
|
||||||
{
|
{
|
||||||
_coordinator = CreateWindowsSubcomponents ();
|
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_coordinator = CreateNetSubcomponents ();
|
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
|
||||||
}
|
}
|
||||||
|
|
||||||
_coordinator.StartAsync ().Wait ();
|
_coordinator.StartAsync ().Wait ();
|
||||||
@@ -109,49 +114,23 @@ public class ApplicationV2 : ApplicationImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMainLoopCoordinator CreateWindowsSubcomponents ()
|
private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
|
||||||
{
|
{
|
||||||
ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ();
|
ConcurrentQueue<T> inputBuffer = new ();
|
||||||
MainLoop<WindowsConsole.InputRecord> loop = new ();
|
MainLoop<T> loop = new ();
|
||||||
|
|
||||||
IComponentFactory<WindowsConsole.InputRecord> cf;
|
IComponentFactory<T> cf;
|
||||||
|
|
||||||
if (_componentFactory != null)
|
if (_componentFactory is IComponentFactory<T> typedFactory)
|
||||||
{
|
{
|
||||||
cf = (IComponentFactory<WindowsConsole.InputRecord>)_componentFactory;
|
cf = typedFactory;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cf = new WindowsComponentFactory ();
|
cf = fallbackFactory ();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MainLoopCoordinator<WindowsConsole.InputRecord> (_timedEvents,
|
return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
|
||||||
inputBuffer,
|
|
||||||
loop,
|
|
||||||
cf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IMainLoopCoordinator CreateNetSubcomponents ()
|
|
||||||
{
|
|
||||||
ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ();
|
|
||||||
MainLoop<ConsoleKeyInfo> loop = new ();
|
|
||||||
|
|
||||||
IComponentFactory<ConsoleKeyInfo> cf;
|
|
||||||
|
|
||||||
if (_componentFactory != null)
|
|
||||||
{
|
|
||||||
cf = (IComponentFactory<ConsoleKeyInfo>)_componentFactory;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cf = new NetComponentFactory ();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MainLoopCoordinator<ConsoleKeyInfo> (
|
|
||||||
_timedEvents,
|
|
||||||
inputBuffer,
|
|
||||||
loop,
|
|
||||||
cf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -260,16 +260,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual string GetVersionInfo ()
|
public virtual string GetVersionInfo ()
|
||||||
{
|
{
|
||||||
var type = "";
|
string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
|
||||||
|
|
||||||
if (InputProcessor is WindowsInputProcessor)
|
|
||||||
{
|
|
||||||
type = "win";
|
|
||||||
}
|
|
||||||
else if (InputProcessor is NetInputProcessor)
|
|
||||||
{
|
|
||||||
type = "net";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "v2" + type;
|
return "v2" + type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ public interface IInputProcessor
|
|||||||
/// <summary>Event fired when a mouse event occurs.</summary>
|
/// <summary>Event fired when a mouse event occurs.</summary>
|
||||||
event EventHandler<MouseEventArgs>? MouseEvent;
|
event EventHandler<MouseEventArgs>? MouseEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the driver associated with this input processor.
|
||||||
|
/// </summary>
|
||||||
|
string DriverName { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
|
/// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
|
||||||
/// <see cref="OnKeyUp"/>.
|
/// <see cref="OnKeyUp"/>.
|
||||||
|
|||||||
3
Terminal.Gui/Drivers/V2/IUnixInput.cs
Normal file
3
Terminal.Gui/Drivers/V2/IUnixInput.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
|
internal interface IUnixInput : IConsoleInput<char>;
|
||||||
@@ -30,6 +30,9 @@ public abstract class InputProcessor<T> : IInputProcessor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ConcurrentQueue<T> InputBuffer { get; }
|
public ConcurrentQueue<T> InputBuffer { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DriverName { get; init; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IAnsiResponseParser GetParser () { return Parser; }
|
public IAnsiResponseParser GetParser () { return Parser; }
|
||||||
|
|
||||||
|
|||||||
@@ -70,10 +70,23 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FlushConsoleInput ()
|
||||||
|
{
|
||||||
|
if (!ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
while (Console.KeyAvailable)
|
||||||
|
{
|
||||||
|
Console.ReadKey (intercept: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Dispose ()
|
public override void Dispose ()
|
||||||
{
|
{
|
||||||
base.Dispose ();
|
base.Dispose ();
|
||||||
|
|
||||||
|
// Disable mouse events first
|
||||||
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
|
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
|
||||||
|
|
||||||
//Disable alternative screen buffer.
|
//Disable alternative screen buffer.
|
||||||
@@ -83,5 +96,8 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
|||||||
Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
|
Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
|
||||||
|
|
||||||
_adjustConsole?.Cleanup ();
|
_adjustConsole?.Cleanup ();
|
||||||
|
|
||||||
|
// Flush any pending input so no stray events appear
|
||||||
|
FlushConsoleInput ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
|
|||||||
#pragma warning restore CA2211
|
#pragma warning restore CA2211
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ()) { }
|
public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
|
||||||
|
{
|
||||||
|
DriverName = "net";
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void Process (ConsoleKeyInfo consoleKeyInfo)
|
protected override void Process (ConsoleKeyInfo consoleKeyInfo)
|
||||||
|
|||||||
@@ -54,24 +54,31 @@ public class NetOutput : OutputBase, IConsoleOutput
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
||||||
{
|
{
|
||||||
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
if (Application.Force16Colors)
|
||||||
output,
|
{
|
||||||
attr.Foreground.R,
|
output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
|
||||||
attr.Foreground.G,
|
output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
|
||||||
attr.Foreground.B
|
}
|
||||||
);
|
else
|
||||||
|
{
|
||||||
|
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
||||||
|
output,
|
||||||
|
attr.Foreground.R,
|
||||||
|
attr.Foreground.G,
|
||||||
|
attr.Foreground.B
|
||||||
|
);
|
||||||
|
|
||||||
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
||||||
output,
|
output,
|
||||||
attr.Background.R,
|
attr.Background.R,
|
||||||
attr.Background.G,
|
attr.Background.G,
|
||||||
attr.Background.B
|
attr.Background.B
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
|
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Write (StringBuilder output)
|
protected override void Write (StringBuilder output)
|
||||||
{
|
{
|
||||||
@@ -116,9 +123,25 @@ public class NetOutput : OutputBase, IConsoleOutput
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
|
||||||
|
|
||||||
/// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
|
/// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
|
||||||
public override void SetCursorVisibility (CursorVisibility visibility)
|
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||||
{
|
{
|
||||||
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
|
if (visibility != CursorVisibility.Invisible)
|
||||||
|
{
|
||||||
|
if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
|
||||||
|
{
|
||||||
|
_currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
|
||||||
|
|
||||||
|
Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
|
||||||
|
}
|
||||||
|
|
||||||
|
Write (EscSeqUtils.CSI_ShowCursor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write (EscSeqUtils.CSI_HideCursor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
Terminal.Gui/Drivers/V2/UnixComponentFactory.cs
Normal file
29
Terminal.Gui/Drivers/V2/UnixComponentFactory.cs
Normal file
@@ -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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
266
Terminal.Gui/Drivers/V2/UnixInput.cs
Normal file
266
Terminal.Gui/Drivers/V2/UnixInput.cs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
|
internal class UnixInput : ConsoleInput<char>, IUnixInput
|
||||||
|
{
|
||||||
|
private const int STDIN_FILENO = 0;
|
||||||
|
|
||||||
|
[StructLayout (LayoutKind.Sequential)]
|
||||||
|
private struct Termios
|
||||||
|
{
|
||||||
|
public uint c_iflag;
|
||||||
|
public uint c_oflag;
|
||||||
|
public uint c_cflag;
|
||||||
|
public uint c_lflag;
|
||||||
|
|
||||||
|
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 32)]
|
||||||
|
public byte [] c_cc;
|
||||||
|
|
||||||
|
public uint c_ispeed;
|
||||||
|
public uint c_ospeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int tcgetattr (int fd, out Termios termios);
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int tcsetattr (int fd, int optional_actions, ref Termios termios);
|
||||||
|
|
||||||
|
// try cfmakeraw (glibc and macOS usually export it)
|
||||||
|
[DllImport ("libc", EntryPoint = "cfmakeraw", SetLastError = false)]
|
||||||
|
private static extern void cfmakeraw_ref (ref Termios termios);
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern nint strerror (int err);
|
||||||
|
|
||||||
|
private const int TCSANOW = 0;
|
||||||
|
|
||||||
|
private const ulong BRKINT = 0x00000002;
|
||||||
|
private const ulong ICRNL = 0x00000100;
|
||||||
|
private const ulong INPCK = 0x00000010;
|
||||||
|
private const ulong ISTRIP = 0x00000020;
|
||||||
|
private const ulong IXON = 0x00000400;
|
||||||
|
|
||||||
|
private const ulong OPOST = 0x00000001;
|
||||||
|
|
||||||
|
private const ulong ECHO = 0x00000008;
|
||||||
|
private const ulong ICANON = 0x00000100;
|
||||||
|
private const ulong IEXTEN = 0x00008000;
|
||||||
|
private const ulong ISIG = 0x00000001;
|
||||||
|
|
||||||
|
private const ulong CS8 = 0x00000030;
|
||||||
|
|
||||||
|
private Termios _original;
|
||||||
|
|
||||||
|
[StructLayout (LayoutKind.Sequential)]
|
||||||
|
private struct Pollfd
|
||||||
|
{
|
||||||
|
public int fd;
|
||||||
|
public short events;
|
||||||
|
public readonly short revents; // readonly signals "don't touch this in managed code"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions.</summary>
|
||||||
|
[Flags]
|
||||||
|
private enum Condition : short
|
||||||
|
{
|
||||||
|
/// <summary>There is data to read</summary>
|
||||||
|
PollIn = 1,
|
||||||
|
|
||||||
|
/// <summary>There is urgent data to read</summary>
|
||||||
|
PollPri = 2,
|
||||||
|
|
||||||
|
/// <summary>Writing to the specified descriptor will not block</summary>
|
||||||
|
PollOut = 4,
|
||||||
|
|
||||||
|
/// <summary>Error condition on output</summary>
|
||||||
|
PollErr = 8,
|
||||||
|
|
||||||
|
/// <summary>Hang-up on output</summary>
|
||||||
|
PollHup = 16,
|
||||||
|
|
||||||
|
/// <summary>File descriptor is not open.</summary>
|
||||||
|
PollNval = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int poll ([In][Out] Pollfd [] ufds, uint nfds, int timeout);
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int read (int fd, byte [] buf, int count);
|
||||||
|
|
||||||
|
// File descriptor for stdout
|
||||||
|
private const int STDOUT_FILENO = 1;
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int write (int fd, byte [] buf, int count);
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int tcflush (int fd, int queueSelector);
|
||||||
|
|
||||||
|
private const int TCIFLUSH = 0; // flush data received but not read
|
||||||
|
|
||||||
|
private Pollfd [] _pollMap;
|
||||||
|
|
||||||
|
public UnixInput ()
|
||||||
|
{
|
||||||
|
Logging.Logger.LogInformation ($"Creating {nameof (UnixInput)}");
|
||||||
|
|
||||||
|
if (ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pollMap = new Pollfd [1];
|
||||||
|
_pollMap [0].fd = STDIN_FILENO; // stdin
|
||||||
|
_pollMap [0].events = (short)Condition.PollIn;
|
||||||
|
|
||||||
|
EnableRawModeAndTreatControlCAsInput ();
|
||||||
|
|
||||||
|
//Enable alternative screen buffer.
|
||||||
|
WriteRaw (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
|
||||||
|
|
||||||
|
//Set cursor key to application.
|
||||||
|
WriteRaw (EscSeqUtils.CSI_HideCursor);
|
||||||
|
|
||||||
|
WriteRaw (EscSeqUtils.CSI_EnableMouseEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnableRawModeAndTreatControlCAsInput ()
|
||||||
|
{
|
||||||
|
if (tcgetattr (STDIN_FILENO, out _original) != 0)
|
||||||
|
{
|
||||||
|
var e = Marshal.GetLastWin32Error ();
|
||||||
|
throw new InvalidOperationException ($"tcgetattr failed errno={e} ({StrError (e)})");
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw = _original;
|
||||||
|
|
||||||
|
// Prefer cfmakeraw if available
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cfmakeraw_ref (ref raw);
|
||||||
|
}
|
||||||
|
catch (EntryPointNotFoundException)
|
||||||
|
{
|
||||||
|
// fallback: roughly cfmakeraw equivalent
|
||||||
|
raw.c_iflag &= ~((uint)BRKINT | (uint)ICRNL | (uint)INPCK | (uint)ISTRIP | (uint)IXON);
|
||||||
|
raw.c_oflag &= ~(uint)OPOST;
|
||||||
|
raw.c_cflag |= (uint)CS8;
|
||||||
|
raw.c_lflag &= ~((uint)ECHO | (uint)ICANON | (uint)IEXTEN | (uint)ISIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcsetattr (STDIN_FILENO, TCSANOW, ref raw) != 0)
|
||||||
|
{
|
||||||
|
var e = Marshal.GetLastWin32Error ();
|
||||||
|
throw new InvalidOperationException ($"tcsetattr failed errno={e} ({StrError (e)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string StrError (int err)
|
||||||
|
{
|
||||||
|
var p = strerror (err);
|
||||||
|
return p == nint.Zero ? $"errno={err}" : Marshal.PtrToStringAnsi (p) ?? $"errno={err}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override bool Peek ()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = poll (_pollMap!, (uint)_pollMap!.Length, 0);
|
||||||
|
|
||||||
|
if (n != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Optionally log the exception
|
||||||
|
Logging.Logger.LogError ($"Error in Peek: {ex.Message}");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void WriteRaw (string text)
|
||||||
|
{
|
||||||
|
if (!ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
byte [] utf8 = Encoding.UTF8.GetBytes (text);
|
||||||
|
// Write to stdout (fd 1)
|
||||||
|
write (STDOUT_FILENO, utf8, utf8.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override IEnumerable<char> Read ()
|
||||||
|
{
|
||||||
|
while (poll (_pollMap!, (uint)_pollMap!.Length, 0) != 0)
|
||||||
|
{
|
||||||
|
// Check if stdin has data
|
||||||
|
if ((_pollMap [0].revents & (int)Condition.PollIn) != 0)
|
||||||
|
{
|
||||||
|
var buf = new byte [256];
|
||||||
|
int bytesRead = read (0, buf, buf.Length); // Read from stdin
|
||||||
|
string input = Encoding.UTF8.GetString (buf, 0, bytesRead);
|
||||||
|
|
||||||
|
foreach (char ch in input)
|
||||||
|
{
|
||||||
|
yield return ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushConsoleInput ()
|
||||||
|
{
|
||||||
|
if (!ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
var fds = new Pollfd [1];
|
||||||
|
fds [0].fd = STDIN_FILENO;
|
||||||
|
fds [0].events = (short)Condition.PollIn;
|
||||||
|
var buf = new byte [256];
|
||||||
|
while (poll (fds, 1, 0) > 0)
|
||||||
|
{
|
||||||
|
read (STDIN_FILENO, buf, buf.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Dispose ()
|
||||||
|
{
|
||||||
|
base.Dispose ();
|
||||||
|
|
||||||
|
if (!ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
// Disable mouse events first
|
||||||
|
WriteRaw (EscSeqUtils.CSI_DisableMouseEvents);
|
||||||
|
|
||||||
|
// Drain any pending input already queued by the terminal
|
||||||
|
FlushConsoleInput ();
|
||||||
|
|
||||||
|
// Flush kernel input buffer
|
||||||
|
tcflush (STDIN_FILENO, TCIFLUSH);
|
||||||
|
|
||||||
|
//Disable alternative screen buffer.
|
||||||
|
WriteRaw (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
|
||||||
|
|
||||||
|
//Set cursor key to cursor.
|
||||||
|
WriteRaw (EscSeqUtils.CSI_ShowCursor);
|
||||||
|
|
||||||
|
// Restore terminal to original state
|
||||||
|
tcsetattr (STDIN_FILENO, TCSANOW, ref _original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Terminal.Gui/Drivers/V2/UnixInputProcessor.cs
Normal file
38
Terminal.Gui/Drivers/V2/UnixInputProcessor.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Input processor for <see cref="UnixInput"/>, deals in <see cref="char"/> stream.
|
||||||
|
/// </summary>
|
||||||
|
internal class UnixInputProcessor : InputProcessor<char>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public UnixInputProcessor (ConcurrentQueue<char> inputBuffer) : base (inputBuffer, new UnixKeyConverter ())
|
||||||
|
{
|
||||||
|
DriverName = "unix";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Process (char input)
|
||||||
|
{
|
||||||
|
foreach (Tuple<char, char> released in Parser.ProcessInput (Tuple.Create (input, input)))
|
||||||
|
{
|
||||||
|
ProcessAfterParsing (released.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void ProcessAfterParsing (char input)
|
||||||
|
{
|
||||||
|
var key = KeyConverter.ToKey (input);
|
||||||
|
|
||||||
|
// If the key is not valid, we don't want to raise any events.
|
||||||
|
if (IsValidInput (key, out key))
|
||||||
|
{
|
||||||
|
OnKeyDown (key);
|
||||||
|
OnKeyUp (key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Terminal.Gui/Drivers/V2/UnixKeyConverter.cs
Normal file
20
Terminal.Gui/Drivers/V2/UnixKeyConverter.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="IKeyConverter{T}"/> capable of converting the
|
||||||
|
/// unix native <see cref="char"/> class
|
||||||
|
/// into Terminal.Gui shared <see cref="Key"/> representation
|
||||||
|
/// (used by <see cref="View"/> etc).
|
||||||
|
/// </summary>
|
||||||
|
internal class UnixKeyConverter : IKeyConverter<char>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Key ToKey (char value)
|
||||||
|
{
|
||||||
|
ConsoleKeyInfo adjustedInput = EscSeqUtils.MapChar (value);
|
||||||
|
|
||||||
|
return EscSeqUtils.MapKey (adjustedInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
175
Terminal.Gui/Drivers/V2/UnixOutput.cs
Normal file
175
Terminal.Gui/Drivers/V2/UnixOutput.cs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
|
||||||
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
|
internal class UnixOutput : OutputBase, IConsoleOutput
|
||||||
|
{
|
||||||
|
[StructLayout (LayoutKind.Sequential)]
|
||||||
|
private struct WinSize
|
||||||
|
{
|
||||||
|
public ushort ws_row;
|
||||||
|
public ushort ws_col;
|
||||||
|
public ushort ws_xpixel;
|
||||||
|
public ushort ws_ypixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly uint TIOCGWINSZ =
|
||||||
|
RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ||
|
||||||
|
RuntimeInformation.IsOSPlatform (OSPlatform.FreeBSD)
|
||||||
|
? 0x40087468u // Darwin/BSD
|
||||||
|
: 0x5413u; // Linux
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int ioctl (int fd, uint request, out WinSize ws);
|
||||||
|
|
||||||
|
// File descriptor for stdout
|
||||||
|
private const int STDOUT_FILENO = 1;
|
||||||
|
|
||||||
|
[DllImport ("libc")]
|
||||||
|
private static extern int write (int fd, byte [] buf, int n);
|
||||||
|
|
||||||
|
[DllImport ("libc", SetLastError = true)]
|
||||||
|
private static extern int dup (int fd);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
||||||
|
{
|
||||||
|
if (Application.Force16Colors)
|
||||||
|
{
|
||||||
|
output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
|
||||||
|
output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
||||||
|
output,
|
||||||
|
attr.Foreground.R,
|
||||||
|
attr.Foreground.G,
|
||||||
|
attr.Foreground.B
|
||||||
|
);
|
||||||
|
|
||||||
|
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
||||||
|
output,
|
||||||
|
attr.Background.R,
|
||||||
|
attr.Background.G,
|
||||||
|
attr.Background.B
|
||||||
|
);
|
||||||
|
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Write (StringBuilder output)
|
||||||
|
{
|
||||||
|
byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());
|
||||||
|
// Write to stdout (fd 1)
|
||||||
|
write (STDOUT_FILENO, utf8, utf8.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point? _lastCursorPosition;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
|
||||||
|
{
|
||||||
|
if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == screenPositionX && _lastCursorPosition.Value.Y == screenPositionY)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastCursorPosition = new (screenPositionX, screenPositionY);
|
||||||
|
|
||||||
|
using var writer = CreateUnixStdoutWriter ();
|
||||||
|
|
||||||
|
// + 1 is needed because Unix is based on 1 instead of 0 and
|
||||||
|
EscSeqUtils.CSI_WriteCursorPosition (writer, screenPositionY + 1, screenPositionX + 1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextWriter CreateUnixStdoutWriter ()
|
||||||
|
{
|
||||||
|
// duplicate stdout so we don’t mess with Console.Out’s FD
|
||||||
|
int fdCopy = dup (STDOUT_FILENO);
|
||||||
|
|
||||||
|
if (fdCopy == -1)
|
||||||
|
{
|
||||||
|
throw new IOException ("Failed to dup STDOUT_FILENO");
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the raw fd into a SafeFileHandle
|
||||||
|
var handle = new SafeFileHandle (fdCopy, ownsHandle: true);
|
||||||
|
|
||||||
|
// create FileStream from the safe handle
|
||||||
|
var stream = new FileStream (handle, FileAccess.Write);
|
||||||
|
|
||||||
|
return new StreamWriter (stream)
|
||||||
|
{
|
||||||
|
AutoFlush = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Write (ReadOnlySpan<char> text)
|
||||||
|
{
|
||||||
|
if (!ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
byte [] utf8 = Encoding.UTF8.GetBytes (text.ToArray ());
|
||||||
|
// Write to stdout (fd 1)
|
||||||
|
write (STDOUT_FILENO, utf8, utf8.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Size GetWindowSize ()
|
||||||
|
{
|
||||||
|
if (ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
// For unit tests, we return a default size.
|
||||||
|
return Size.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl (1, TIOCGWINSZ, out WinSize ws) == 0)
|
||||||
|
{
|
||||||
|
if (ws.ws_col > 0 && ws.ws_row > 0)
|
||||||
|
{
|
||||||
|
return new (ws.ws_col, ws.ws_row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Size.Empty; // fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
|
||||||
|
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||||
|
{
|
||||||
|
if (visibility != CursorVisibility.Invisible)
|
||||||
|
{
|
||||||
|
if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
|
||||||
|
{
|
||||||
|
_currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
|
||||||
|
|
||||||
|
Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
|
||||||
|
}
|
||||||
|
|
||||||
|
Write (EscSeqUtils.CSI_ShowCursor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write (EscSeqUtils.CSI_HideCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SetCursorPosition (int col, int row)
|
||||||
|
{
|
||||||
|
SetCursorPositionImpl (col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,10 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
|
|||||||
private readonly bool [] _lastWasPressed = new bool[4];
|
private readonly bool [] _lastWasPressed = new bool[4];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter ()) { }
|
public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter ())
|
||||||
|
{
|
||||||
|
DriverName = "win";
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void Process (InputRecord inputEvent)
|
protected override void Process (InputRecord inputEvent)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class SyncrhonizationContextTests
|
|||||||
[InlineData (typeof (CursesDriver))]
|
[InlineData (typeof (CursesDriver))]
|
||||||
[InlineData (typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>), "v2win")]
|
[InlineData (typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>), "v2win")]
|
||||||
[InlineData (typeof (ConsoleDriverFacade<ConsoleKeyInfo>), "v2net")]
|
[InlineData (typeof (ConsoleDriverFacade<ConsoleKeyInfo>), "v2net")]
|
||||||
|
[InlineData (typeof (ConsoleDriverFacade<char>), "v2unix")]
|
||||||
public void SynchronizationContext_Post (Type driverType, string driverName = null)
|
public void SynchronizationContext_Post (Type driverType, string driverName = null)
|
||||||
{
|
{
|
||||||
lock (_lockPost)
|
lock (_lockPost)
|
||||||
|
|||||||
@@ -103,16 +103,27 @@ public class AnsiKeyboardParserTests
|
|||||||
yield return new object [] { "\u001b[1;2P", Key.F1.WithShift };
|
yield return new object [] { "\u001b[1;2P", Key.F1.WithShift };
|
||||||
yield return new object [] { "\u001b[1;3Q", Key.F2.WithAlt };
|
yield return new object [] { "\u001b[1;3Q", Key.F2.WithAlt };
|
||||||
yield return new object [] { "\u001b[1;5R", Key.F3.WithCtrl };
|
yield return new object [] { "\u001b[1;5R", Key.F3.WithCtrl };
|
||||||
|
|
||||||
|
// Keys with Alt modifiers
|
||||||
|
yield return new object [] { "\u001ba", Key.A.WithAlt, true };
|
||||||
|
yield return new object [] { "\u001bA", Key.A.WithShift.WithAlt, true };
|
||||||
|
yield return new object [] { "\u001b1", Key.D1.WithAlt, true };
|
||||||
|
|
||||||
|
// Keys with Ctrl and Alt modifiers
|
||||||
|
yield return new object [] { "\u001b\u0001", Key.A.WithCtrl.WithAlt, true };
|
||||||
|
yield return new object [] { "\u001b\u001a", Key.Z.WithCtrl.WithAlt, true };
|
||||||
|
|
||||||
|
// Keys with Ctrl, Shift and Alt modifiers
|
||||||
|
yield return new object [] { "\u001b\u001f", Key.D7.WithCtrl.WithShift.WithAlt, true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consolidated test for all keyboard events (e.g., arrow keys)
|
// Consolidated test for all keyboard events (e.g., arrow keys)
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData (nameof (GetKeyboardTestData))]
|
[MemberData (nameof (GetKeyboardTestData))]
|
||||||
public void ProcessKeyboardInput_ReturnsCorrectKey (string? input, Key? expectedKey)
|
public void ProcessKeyboardInput_ReturnsCorrectKey (string? input, Key? expectedKey, bool isLastMinute = false)
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
Key? result = _parser.IsKeyboard (input)?.GetKey (input);
|
Key? result = _parser.IsKeyboard (input, isLastMinute)?.GetKey (input);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one
|
Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one
|
||||||
|
|||||||
@@ -1538,6 +1538,22 @@ public class EscSeqUtilsTests
|
|||||||
Assert.Equal (expected, actual);
|
Assert.Equal (expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData ('\u001B', KeyCode.Esc)]
|
||||||
|
[InlineData ('\r', KeyCode.Enter)]
|
||||||
|
[InlineData ('1', KeyCode.D1)]
|
||||||
|
[InlineData ('!', (KeyCode)'!')]
|
||||||
|
[InlineData ('a', KeyCode.A)]
|
||||||
|
[InlineData ('A', KeyCode.A | KeyCode.ShiftMask)]
|
||||||
|
public void MapChar_Returns_Modifiers_If_Needed (char ch, KeyCode keyCode)
|
||||||
|
{
|
||||||
|
ConsoleKeyInfo cki = EscSeqUtils.MapChar (ch);
|
||||||
|
Key key = EscSeqUtils.MapKey (cki);
|
||||||
|
Key expectedKey = keyCode;
|
||||||
|
|
||||||
|
Assert.Equal (key, expectedKey);
|
||||||
|
}
|
||||||
|
|
||||||
private void ClearAll ()
|
private void ClearAll ()
|
||||||
{
|
{
|
||||||
EscSeqRequests.Clear ();
|
EscSeqRequests.Clear ();
|
||||||
|
|||||||
Reference in New Issue
Block a user