Merge pull request #1042 from BDisp/netdriver-all-events

Fixes #1041. NetDriver events should stay all together.
This commit is contained in:
Charlie Kindel
2020-12-21 11:47:22 -07:00
committed by GitHub
3 changed files with 311 additions and 125 deletions

View File

@@ -1,4 +1,5 @@
//
//#define PROCESS_REQUEST
//
// NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
//
// Authors:
@@ -107,24 +108,40 @@ namespace Terminal.Gui {
internal class NetEvents {
ManualResetEventSlim inputReady = new ManualResetEventSlim (false);
ManualResetEventSlim waitForStart = new ManualResetEventSlim (false);
ManualResetEventSlim winChange = new ManualResetEventSlim (false);
Queue<InputResult?> inputResultQueue = new Queue<InputResult?> ();
ConsoleDriver consoleDriver;
int lastWindowHeight;
int largestWindowHeight;
#if PROCESS_REQUEST
bool neededProcessRequest;
#endif
public int NumberOfCSI { get; }
public NetEvents (int numberOfCSI = 1)
public NetEvents (ConsoleDriver consoleDriver, int numberOfCSI = 1)
{
if (consoleDriver == null) {
throw new ArgumentNullException ("Console driver instance must be provided.");
}
this.consoleDriver = consoleDriver;
NumberOfCSI = numberOfCSI;
Task.Run (ProcessInputResultQueue);
Task.Run (CheckWinChange);
}
public InputResult? ReadConsoleInput ()
{
while (true) {
waitForStart.Set ();
winChange.Set ();
if (inputResultQueue.Count == 0) {
inputReady.Wait ();
inputReady.Reset ();
}
#if PROCESS_REQUEST
neededProcessRequest = false;
#endif
if (inputResultQueue.Count > 0) {
return inputResultQueue.Dequeue ();
}
@@ -145,6 +162,67 @@ namespace Terminal.Gui {
}
}
void CheckWinChange ()
{
while (true) {
winChange.Wait ();
winChange.Reset ();
WaitWinChange ();
inputReady.Set ();
}
}
void WaitWinChange ()
{
while (true) {
if (!consoleDriver.HeightAsBuffer) {
if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) {
var w = Math.Max (Console.WindowWidth, 0);
var h = Math.Max (Console.WindowHeight, 0);
GetWindowSizeEvent (new Size (w, h));
return;
}
} else {
largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
if (Console.BufferWidth != consoleDriver.Cols || largestWindowHeight != consoleDriver.Rows
|| Console.WindowHeight != lastWindowHeight) {
lastWindowHeight = Console.WindowHeight;
GetWindowSizeEvent (new Size (Console.BufferWidth, lastWindowHeight));
return;
}
if (Console.WindowTop != consoleDriver.Top) {
// Top only working on Windows.
var winPositionEv = new WindowPositionEvent () {
Top = Console.WindowTop
};
inputResultQueue.Enqueue (new InputResult () {
EventType = EventType.WindowPosition,
WindowPositionEvent = winPositionEv
});
return;
}
#if PROCESS_REQUEST
if (!neededProcessRequest) {
Console.Out.Write ("\x1b[6n");
neededProcessRequest = true;
}
#endif
}
}
}
void GetWindowSizeEvent (Size size)
{
WindowSizeEvent windowSizeEvent = new WindowSizeEvent () {
Size = size
};
inputResultQueue.Enqueue (new InputResult () {
EventType = EventType.WindowSize,
WindowSizeEvent = windowSizeEvent
});
}
void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
{
InputResult inputResult = new InputResult {
@@ -174,6 +252,7 @@ namespace Terminal.Gui {
}
break;
case 27:
case 91:
ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo };
ConsoleModifiers mod = consoleKeyInfo.Modifiers;
while (Console.KeyAvailable) {
@@ -207,30 +286,67 @@ namespace Terminal.Gui {
ConsoleKeyInfo [] splitedCki = new ConsoleKeyInfo [] { };
int length = 0;
var kChar = GetKeyCharArray (cki);
var nCSI = kChar.Where (val => val == '\x1b').ToArray ().Length;
var nCSI = GetNumberOfCSI (kChar);
int curCSI = 0;
char previousKChar = '\0';
if (nCSI > 1) {
foreach (var ck in cki) {
if (NumberOfCSI > 0 && nCSI - curCSI > NumberOfCSI) {
if (ck.KeyChar == '\x1b') {
if (ck.KeyChar == '\x1b'
|| (ck.KeyChar == '[' && previousKChar != '\x1b')) {
curCSI++;
}
previousKChar = ck.KeyChar;
continue;
}
if (ck.KeyChar == '\x1b') {
if (splitedCki.Length > 0) {
if (ck.KeyChar == '\x1b' || (ck.KeyChar == '[' && previousKChar != '\x1b')) {
if (ck.KeyChar == 'R') {
ResizeArray (ck);
}
if (splitedCki.Length > 1) {
DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod);
}
splitedCki = new ConsoleKeyInfo [] { };
length = 0;
}
length++;
Array.Resize (ref splitedCki, length);
splitedCki [length - 1] = ck;
ResizeArray (ck);
previousKChar = ck.KeyChar;
}
} else {
DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, cki, ref mod);
}
void ResizeArray (ConsoleKeyInfo ck)
{
length++;
Array.Resize (ref splitedCki, length);
splitedCki [length - 1] = ck;
}
}
char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
{
char [] kChar = new char [] { };
var length = 0;
foreach (var kc in cki) {
length++;
Array.Resize (ref kChar, length);
kChar [length - 1] = kc.KeyChar;
}
return kChar;
}
int GetNumberOfCSI (char [] csi)
{
int nCSI = 0;
for (int i = 0; i < csi.Length; i++) {
if (csi [i] == '\x1b' || (csi [i] == '[' && (i == 0 || (i > 0 && csi [i - 1] != '\x1b')))) {
nCSI++;
}
}
return nCSI;
}
void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ref MouseEvent mouseEvent, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod)
@@ -306,7 +422,8 @@ namespace Terminal.Gui {
(mod & ConsoleModifiers.Control) != 0);
break;
case 7:
throw new NotImplementedException ("Condition not yet detected!");
GetRequestEvent (GetKeyCharArray (cki));
return;
case int n when n >= 8:
GetMouseEvent (cki);
return;
@@ -320,6 +437,56 @@ namespace Terminal.Gui {
inputResultQueue.Enqueue (inputResult);
}
Point lastCursorPosition;
void GetRequestEvent (char [] kChar)
{
EventType eventType = new EventType ();
Point point = new Point ();
int foundPoint = 0;
string value = "";
for (int i = 0; i < kChar.Length; i++) {
var c = kChar [i];
if (c == '[') {
foundPoint++;
} else if (foundPoint == 1 && c != ';') {
value += c.ToString ();
} else if (c == ';') {
if (foundPoint == 1) {
point.Y = int.Parse (value) - 1;
}
value = "";
foundPoint++;
} else if (foundPoint > 0 && i < kChar.Length - 1) {
value += c.ToString ();
} else if (i == kChar.Length - 1) {
point.X = int.Parse (value) + Console.WindowTop - 1;
switch (c) {
case 'R':
if (lastCursorPosition.Y != point.Y) {
lastCursorPosition = point;
eventType = EventType.WindowPosition;
var winPositionEv = new WindowPositionEvent () {
CursorPosition = point
};
inputResultQueue.Enqueue (new InputResult () {
EventType = eventType,
WindowPositionEvent = winPositionEv
});
} else {
return;
}
break;
default:
throw new NotImplementedException ();
}
}
}
inputReady.Set ();
}
MouseEvent lastMouseEvent;
bool isButtonPressed;
bool isButtonClicked;
@@ -369,6 +536,7 @@ namespace Terminal.Gui {
case 8:
case 16:
case 32:
case 40:
buttonState = c == 'M' ? MouseButtonState.Button1Pressed
: MouseButtonState.Button1Released;
break;
@@ -376,6 +544,7 @@ namespace Terminal.Gui {
case 9:
case 17:
case 33:
case 41:
buttonState = c == 'M' ? MouseButtonState.Button2Pressed
: MouseButtonState.Button2Released;
break;
@@ -383,6 +552,7 @@ namespace Terminal.Gui {
case 10:
case 18:
case 34:
case 42:
buttonState = c == 'M' ? MouseButtonState.Button3Pressed
: MouseButtonState.Button3Released;
break;
@@ -585,7 +755,7 @@ namespace Terminal.Gui {
await Task.Delay (200);
var view = Application.wantContinuousButtonPressedView;
if (isButtonPressed && !Console.KeyAvailable
&& !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
&& !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
&& (view != null || view == null && lastMouseEvent.Position != point)) {
point = lastMouseEvent.Position;
inputResultQueue.Enqueue (new InputResult () {
@@ -601,19 +771,6 @@ namespace Terminal.Gui {
isButtonPressed = false;
}
char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
{
char [] kChar = new char [] { };
var length = 0;
foreach (var kc in cki) {
length++;
Array.Resize (ref kChar, length);
kChar [length - 1] = kc.KeyChar;
}
return kChar;
}
ConsoleModifiers GetConsoleModifiers (uint keyChar)
{
switch (keyChar) {
@@ -719,7 +876,9 @@ namespace Terminal.Gui {
public enum EventType {
key = 1,
Mouse = 2
Mouse = 2,
WindowSize = 3,
WindowPosition = 4
}
[Flags]
@@ -760,10 +919,21 @@ namespace Terminal.Gui {
public MouseButtonState ButtonState;
}
public struct WindowSizeEvent {
public Size Size;
}
public struct WindowPositionEvent {
public int Top;
public Point CursorPosition;
}
public struct InputResult {
public EventType EventType;
public ConsoleKeyInfo ConsoleKeyInfo;
public MouseEvent MouseEvent;
public WindowSizeEvent WindowSizeEvent;
public WindowPositionEvent WindowPositionEvent;
}
}
@@ -775,16 +945,19 @@ namespace Terminal.Gui {
public override bool HeightAsBuffer { get; set; }
public NetWinVTConsole NetWinConsole { get; }
public bool IsWinPlatform { get; }
public bool AlwaysSetPosition { get; set; }
bool isWinPlatform;
int largestWindowHeight;
public NetDriver ()
{
var p = Environment.OSVersion.Platform;
if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
isWinPlatform = true;
IsWinPlatform = true;
NetWinConsole = new NetWinVTConsole ();
}
largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
}
// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
@@ -848,7 +1021,7 @@ namespace Terminal.Gui {
public override void End ()
{
if (isWinPlatform) {
if (IsWinPlatform) {
NetWinConsole.Cleanup ();
}
@@ -861,6 +1034,8 @@ namespace Terminal.Gui {
{
if (Rows > 0) {
Console.Clear ();
Console.Out.Write ("\x1b[3J");
//Console.Out.Write ("\x1b[?25l");
}
}
@@ -934,7 +1109,7 @@ namespace Terminal.Gui {
// Can raise an exception while is still resizing.
try {
// Not supported on Unix.
if (isWinPlatform) {
if (IsWinPlatform) {
#pragma warning disable CA1416
Console.CursorTop = 0;
Console.CursorLeft = 0;
@@ -954,7 +1129,7 @@ namespace Terminal.Gui {
}
}
} else {
if (isWinPlatform && Console.WindowHeight > 0) {
if (IsWinPlatform && Console.WindowHeight > 0) {
// Can raise an exception while is still resizing.
try {
#pragma warning disable CA1416
@@ -1012,11 +1187,16 @@ namespace Terminal.Gui {
}
}
public override void Refresh ()
{
UpdateScreen ();
}
public override void UpdateScreen ()
{
if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
|| (!HeightAsBuffer && Rows != Console.WindowHeight)
|| (HeightAsBuffer && Rows != Console.BufferHeight)) {
|| (HeightAsBuffer && Rows != largestWindowHeight)) {
return;
}
@@ -1024,6 +1204,7 @@ namespace Terminal.Gui {
int rows = Math.Min (Console.WindowHeight + top, Rows);
int cols = Cols;
Console.CursorVisible = false;
for (int row = top; row < rows; row++) {
if (!dirtyLine [row]) {
continue;
@@ -1033,31 +1214,35 @@ namespace Terminal.Gui {
if (contents [row, col, 2] != 1) {
continue;
}
if (Console.WindowHeight > 0) {
// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
try {
Console.SetCursorPosition (col, row);
} catch (Exception) {
return;
}
if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) {
return;
}
for (; col < cols && contents [row, col, 2] == 1; col++) {
var color = contents [row, col, 1];
if (color != redrawColor) {
SetColor (color);
}
if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
return;
}
Console.Write ((char)contents [row, col, 0]);
contents [row, col, 2] = 0;
}
}
}
Console.CursorVisible = true;
UpdateCursor ();
}
public override void Refresh ()
bool SetCursorPosition (int col, int row)
{
UpdateScreen ();
// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
try {
Console.SetCursorPosition (col, row);
return true;
} catch (Exception) {
return false;
}
}
public override void UpdateCursor ()
@@ -1230,34 +1415,8 @@ namespace Terminal.Gui {
var mLoop = mainLoop.Driver as NetMainLoop;
// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
mLoop.KeyPressed = (e) => ProcessInput (e);
mLoop.WinChanged = (e) => ChangeWin (e);
}
bool winChanging;
void ChangeWin (int newTop)
{
winChanging = true;
const int Min_WindowWidth = 14;
Size size = new Size ();
if (!HeightAsBuffer) {
size = new Size (Math.Max (Min_WindowWidth, Console.WindowWidth),
Console.WindowHeight);
top = 0;
} else {
size = new Size (Console.BufferWidth, Console.BufferHeight);
top = newTop;
}
cols = size.Width;
rows = size.Height;
ResizeScreen ();
UpdateOffScreen ();
if (!winChanging) {
TerminalResized.Invoke ();
}
// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
mLoop.ProcessInput = (e) => ProcessInput (e);
}
void ProcessInput (NetEvents.InputResult inputEvent)
@@ -1276,6 +1435,40 @@ namespace Terminal.Gui {
case NetEvents.EventType.Mouse:
mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
break;
case NetEvents.EventType.WindowSize:
ChangeWin ();
break;
case NetEvents.EventType.WindowPosition:
var newTop = inputEvent.WindowPositionEvent.Top;
if (HeightAsBuffer && top != newTop) {
top = newTop;
Refresh ();
}
break;
}
}
bool winChanging;
void ChangeWin ()
{
winChanging = true;
const int Min_WindowWidth = 14;
Size size = new Size ();
if (!HeightAsBuffer) {
size = new Size (Math.Max (Min_WindowWidth, Console.WindowWidth),
Console.WindowHeight);
top = 0;
} else {
largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
size = new Size (Console.BufferWidth, largestWindowHeight);
}
cols = size.Width;
rows = size.Height;
ResizeScreen ();
UpdateOffScreen ();
if (!winChanging) {
TerminalResized.Invoke ();
}
}
@@ -1416,24 +1609,15 @@ namespace Terminal.Gui {
internal class NetMainLoop : IMainLoopDriver {
ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
ManualResetEventSlim winChange = new ManualResetEventSlim (false);
Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
MainLoop mainLoop;
ConsoleDriver consoleDriver;
bool winChanged;
int newTop;
CancellationTokenSource tokenSource = new CancellationTokenSource ();
NetEvents netEvents;
/// <summary>
/// Invoked when a Key is pressed.
/// </summary>
public Action<NetEvents.InputResult> KeyPressed;
/// <summary>
/// Invoked when the window is changed.
/// </summary>
public Action<int> WinChanged;
public Action<NetEvents.InputResult> ProcessInput;
/// <summary>
/// Initializes the class with the console driver.
@@ -1447,11 +1631,10 @@ namespace Terminal.Gui {
if (consoleDriver == null) {
throw new ArgumentNullException ("Console driver instance must be provided.");
}
this.consoleDriver = consoleDriver;
netEvents = new NetEvents ();
netEvents = new NetEvents (consoleDriver);
}
void KeyReader ()
void NetInputHandler ()
{
while (true) {
waitForProbe.Wait ();
@@ -1470,43 +1653,10 @@ namespace Terminal.Gui {
}
}
void CheckWinChange ()
{
while (true) {
winChange.Wait ();
winChange.Reset ();
WaitWinChange ();
winChanged = true;
keyReady.Set ();
}
}
int lastWindowHeight;
void WaitWinChange ()
{
while (true) {
if (!consoleDriver.HeightAsBuffer) {
if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) {
return;
}
} else {
if (Console.BufferWidth != consoleDriver.Cols || Console.BufferHeight != consoleDriver.Rows
|| Console.WindowTop != consoleDriver.Top
|| Console.WindowHeight != lastWindowHeight) {
// Top only working on Windows.
newTop = Console.WindowTop;
lastWindowHeight = Console.WindowHeight;
return;
}
}
}
}
void IMainLoopDriver.Setup (MainLoop mainLoop)
{
this.mainLoop = mainLoop;
Task.Run (KeyReader);
Task.Run (CheckWinChange);
Task.Run (NetInputHandler);
}
void IMainLoopDriver.Wakeup ()
@@ -1517,7 +1667,6 @@ namespace Terminal.Gui {
bool IMainLoopDriver.EventsPending (bool wait)
{
waitForProbe.Set ();
winChange.Set ();
if (CheckTimers (wait, out var waitTimeout)) {
return true;
@@ -1534,7 +1683,7 @@ namespace Terminal.Gui {
}
if (!tokenSource.IsCancellationRequested) {
return inputResult.Count > 0 || CheckTimers (wait, out _) || winChanged;
return inputResult.Count > 0 || CheckTimers (wait, out _);
}
tokenSource.Dispose ();
@@ -1568,11 +1717,7 @@ namespace Terminal.Gui {
void IMainLoopDriver.MainIteration ()
{
if (inputResult.Count > 0) {
KeyPressed?.Invoke (inputResult.Dequeue ().Value);
}
if (winChanged) {
winChanged = false;
WinChanged?.Invoke (newTop);
ProcessInput?.Invoke (inputResult.Dequeue ().Value);
}
}
}

View File

@@ -99,6 +99,24 @@ namespace Terminal.Gui {
}
}
/// <summary>
/// Used only by <see cref="NetDriver"/> to forcing always moving the cursor position when writing to the screen.
/// </summary>
public static bool AlwaysSetPosition {
get {
if (Driver is NetDriver) {
return (Driver as NetDriver).AlwaysSetPosition;
}
return false;
}
set {
if (Driver is NetDriver) {
(Driver as NetDriver).AlwaysSetPosition = value;
Driver.Refresh ();
}
}
}
/// <summary>
/// The <see cref="MainLoop"/> driver for the application
/// </summary>

View File

@@ -63,6 +63,8 @@ namespace UICatalog {
private static Scenario _runningScenario = null;
private static bool _useSystemConsole = false;
private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
private static bool _heightAsBuffer;
private static bool _alwaysSetPosition;
static void Main (string [] args)
{
@@ -144,6 +146,8 @@ namespace UICatalog {
{
Application.UseSystemConsole = _useSystemConsole;
Application.Init ();
Application.HeightAsBuffer = _heightAsBuffer;
Application.AlwaysSetPosition = _alwaysSetPosition;
// Set this here because not initialized until driver is loaded
_baseColorScheme = Colors.Base;
@@ -280,9 +284,27 @@ namespace UICatalog {
menuItems.Add (CreateDiagnosticFlagsMenuItems ());
menuItems.Add (new MenuItem [] { null });
menuItems.Add (CreateSizeStyle ());
menuItems.Add (CreateAlwaysSetPosition ());
return menuItems;
}
static MenuItem [] CreateAlwaysSetPosition ()
{
List<MenuItem> menuItems = new List<MenuItem> ();
var item = new MenuItem ();
item.Title = "_Always set position (NetDriver only)";
item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
item.CheckType |= MenuItemCheckStyle.Checked;
item.Checked = Application.AlwaysSetPosition;
item.Action += () => {
Application.AlwaysSetPosition = !item.Checked;
item.Checked = _alwaysSetPosition = Application.AlwaysSetPosition;
};
menuItems.Add (item);
return menuItems.ToArray ();
}
static MenuItem [] CreateSizeStyle ()
{
List<MenuItem> menuItems = new List<MenuItem> ();
@@ -293,7 +315,8 @@ namespace UICatalog {
item.Checked = Application.HeightAsBuffer;
item.Action += () => {
item.Checked = !item.Checked;
Application.HeightAsBuffer = item.Checked;
_heightAsBuffer = item.Checked;
Application.HeightAsBuffer = _heightAsBuffer;
};
menuItems.Add (item);