Pārlūkot izejas kodu

Trying fixing #518. Almost functions work on both Windows and Unix with the NetDriver.

BDisp 4 gadi atpakaļ
vecāks
revīzija
bcc31e0da0

+ 1 - 1
Example/Example.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 3
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -17,9 +17,9 @@ namespace Terminal.Gui {
 	/// This is the Curses driver for the gui.cs/Terminal framework.
 	/// </summary>
 	internal class CursesDriver : ConsoleDriver {
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 		public override int Cols => Curses.Cols;
 		public override int Rows => Curses.Lines;
+		public override int Top => 0;
 
 		// Current row, and current col, tracked by Move/AddRune only
 		int ccol, crow;
@@ -907,7 +907,5 @@ namespace Terminal.Gui {
 			killpg (0, signal);
 			return true;
 		}
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 	}
-
 }

+ 4 - 82
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -15,15 +15,11 @@ namespace Terminal.Gui {
 	/// Implements a mock ConsoleDriver for unit testing
 	/// </summary>
 	public class FakeDriver : ConsoleDriver {
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 		int cols, rows;
-		/// <summary>
-		/// 
-		/// </summary>
 		public override int Cols => cols;
-		/// <summary>
-		/// 
-		/// </summary>
 		public override int Rows => rows;
+		public override int Top => 0;
 
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		int [,,] contents;
@@ -49,9 +45,6 @@ namespace Terminal.Gui {
 
 		static bool sync = false;
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public FakeDriver ()
 		{
 			cols = FakeConsole.WindowWidth;
@@ -62,11 +55,6 @@ namespace Terminal.Gui {
 		bool needMove;
 		// Current row, and current col, tracked by Move/AddCh only
 		int ccol, crow;
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="col"></param>
-		/// <param name="row"></param>
 		public override void Move (int col, int row)
 		{
 			ccol = col;
@@ -84,10 +72,6 @@ namespace Terminal.Gui {
 
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="rune"></param>
 		public override void AddRune (Rune rune)
 		{
 			rune = MakePrintable (rune);
@@ -113,19 +97,12 @@ namespace Terminal.Gui {
 				UpdateScreen ();
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="str"></param>
 		public override void AddStr (ustring str)
 		{
 			foreach (var rune in str)
 				AddRune (rune);
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void End ()
 		{
 			FakeConsole.ResetColor ();
@@ -138,10 +115,6 @@ namespace Terminal.Gui {
 			return new Attribute () { value = ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff) };
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="terminalResized"></param>
 		public override void Init (Action terminalResized)
 		{
 			Colors.TopLevel = new ColorScheme ();
@@ -185,12 +158,6 @@ namespace Terminal.Gui {
 			//MockConsole.Clear ();
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="fore"></param>
-		/// <param name="back"></param>
-		/// <returns></returns>
 		public override Attribute MakeAttribute (Color fore, Color back)
 		{
 			return MakeColor ((ConsoleColor)fore, (ConsoleColor)back);
@@ -211,9 +178,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void UpdateScreen ()
 		{
 			int rows = Rows;
@@ -233,9 +197,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void Refresh ()
 		{
 			int rows = Rows;
@@ -267,40 +228,24 @@ namespace Terminal.Gui {
 			FakeConsole.CursorLeft = savedCol;
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void UpdateCursor ()
 		{
 			//
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void StartReportingMouseMoves ()
 		{
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void StopReportingMouseMoves ()
 		{
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void Suspend ()
 		{
 		}
 
 		int currentAttribute;
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="c"></param>
 		public override void SetAttribute (Attribute c)
 		{
 			currentAttribute = c.value;
@@ -417,18 +362,10 @@ namespace Terminal.Gui {
 			return keyMod != Key.Null ? keyMod | key : key;
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="mainLoop"></param>
-		/// <param name="keyHandler"></param>
-		/// <param name="keyDownHandler"></param>
-		/// <param name="keyUpHandler"></param>
-		/// <param name="mouseHandler"></param>
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
-			(mainLoop.Driver as NetMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
+			(mainLoop.Driver as FakeMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
 				var map = MapKey (consoleKey);
 				if (map == (Key)0xffffffff)
 					return;
@@ -452,38 +389,23 @@ namespace Terminal.Gui {
 			};
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="foreground"></param>
-		/// <param name="background"></param>
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		{
 			throw new NotImplementedException ();
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="foregroundColorId"></param>
-		/// <param name="backgroundColorId"></param>
 		public override void SetColors (short foregroundColorId, short backgroundColorId)
 		{
 			throw new NotImplementedException ();
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void CookMouse ()
 		{
 		}
 
-		/// <summary>
-		/// 
-		/// </summary>
 		public override void UncookMouse ()
 		{
 		}
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 	}
 }

+ 89 - 0
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Threading;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Mainloop intended to be used with the .NET System.Console API, and can
+	/// be used on Windows and Unix, it is cross platform but lacks things like
+	/// file descriptor monitoring.
+	/// </summary>
+	/// <remarks>
+	/// This implementation is used for FakeDriver.
+	/// </remarks>
+	public class FakeMainLoop : IMainLoopDriver {
+		AutoResetEvent keyReady = new AutoResetEvent (false);
+		AutoResetEvent waitForProbe = new AutoResetEvent (false);
+		ConsoleKeyInfo? keyResult = null;
+		MainLoop mainLoop;
+		Func<ConsoleKeyInfo> consoleKeyReaderFn = null;
+
+		/// <summary>
+		/// Invoked when a Key is pressed.
+		/// </summary>
+		public Action<ConsoleKeyInfo> KeyPressed;
+
+		/// <summary>
+		/// Initializes the class.
+		/// </summary>
+		/// <remarks>
+		///   Passing a consoleKeyReaderfn is provided to support unit test scenarios.
+		/// </remarks>
+		/// <param name="consoleKeyReaderFn">The method to be called to get a key from the console.</param>
+		public FakeMainLoop (Func<ConsoleKeyInfo> consoleKeyReaderFn = null)
+		{
+			if (consoleKeyReaderFn == null) {
+				throw new ArgumentNullException ("key reader function must be provided.");
+			}
+			this.consoleKeyReaderFn = consoleKeyReaderFn;
+		}
+
+		void WindowsKeyReader ()
+		{
+			while (true) {
+				waitForProbe.WaitOne ();
+				keyResult = consoleKeyReaderFn ();
+				keyReady.Set ();
+			}
+		}
+
+		void IMainLoopDriver.Setup (MainLoop mainLoop)
+		{
+			this.mainLoop = mainLoop;
+			Thread readThread = new Thread (WindowsKeyReader);
+			readThread.Start ();
+		}
+
+		void IMainLoopDriver.Wakeup ()
+		{
+		}
+
+		bool IMainLoopDriver.EventsPending (bool wait)
+		{
+			long now = DateTime.UtcNow.Ticks;
+
+			int waitTimeout;
+			if (mainLoop.timeouts.Count > 0) {
+				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+				if (waitTimeout < 0)
+					return true;
+			} else
+				waitTimeout = -1;
+
+			if (!wait)
+				waitTimeout = 0;
+
+			keyResult = null;
+			waitForProbe.Set ();
+			keyReady.WaitOne (waitTimeout);
+			return keyResult.HasValue;
+		}
+
+		void IMainLoopDriver.MainIteration ()
+		{
+			if (keyResult.HasValue) {
+				KeyPressed?.Invoke (keyResult.Value);
+				keyResult = null;
+			}
+		}
+	}
+}

+ 208 - 91
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -8,46 +8,46 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
+using System.Threading.Tasks;
 using NStack;
 
 namespace Terminal.Gui {
 
 	internal class NetDriver : ConsoleDriver {
-		int cols, rows;
+		int cols, rows, top;
 		public override int Cols => cols;
 		public override int Rows => rows;
+		public override int Top => top;
 
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		int [,,] contents;
 		bool [] dirtyLine;
 
+		public NetDriver ()
+		{
+			ResizeScreen ();
+			UpdateOffscreen ();
+		}
+
 		void UpdateOffscreen ()
 		{
 			int cols = Cols;
 			int rows = Rows;
 
 			contents = new int [rows, cols, 3];
-			for (int r = 0; r < rows; r++) {
+			dirtyLine = new bool [rows];
+			for (int row = 0; row < rows; row++) {
 				for (int c = 0; c < cols; c++) {
-					contents [r, c, 0] = ' ';
-					contents [r, c, 1] = MakeColor (ConsoleColor.Gray, ConsoleColor.Black);
-					contents [r, c, 2] = 0;
+					contents [row, c, 0] = ' ';
+					contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
+					contents [row, c, 2] = 0;
+					dirtyLine [row] = true;
 				}
 			}
-			dirtyLine = new bool [rows];
-			for (int row = 0; row < rows; row++)
-				dirtyLine [row] = true;
 		}
 
 		static bool sync = false;
 
-		public NetDriver ()
-		{
-			cols = Console.WindowWidth;
-			rows = Console.WindowHeight - 1;
-			UpdateOffscreen ();
-		}
-
 		bool needMove;
 		// Current row, and current col, tracked by Move/AddCh only
 		int ccol, crow;
@@ -57,15 +57,18 @@ namespace Terminal.Gui {
 			crow = row;
 
 			if (Clip.Contains (col, row)) {
-				Console.CursorTop = row;
-				Console.CursorLeft = col;
-				needMove = false;
+				if (cols == Console.WindowWidth && rows == Console.WindowHeight) {
+					Console.SetCursorPosition (col, row);
+					needMove = false;
+				}
 			} else {
-				Console.CursorTop = Clip.Y;
-				Console.CursorLeft = Clip.X;
-				needMove = true;
+				if (cols == Console.WindowWidth && rows == Console.WindowHeight) {
+					if (Console.WindowHeight > 0) {
+						Console.SetCursorPosition (Clip.X, Clip.Y);
+					}
+					needMove = true;
+				}
 			}
-
 		}
 
 		public override void AddRune (Rune rune)
@@ -73,8 +76,9 @@ namespace Terminal.Gui {
 			rune = MakePrintable (rune);
 			if (Clip.Contains (ccol, crow)) {
 				if (needMove) {
-					//Console.CursorLeft = ccol;
-					//Console.CursorTop = crow;
+					if (cols == Console.WindowWidth && rows == Console.WindowHeight) {
+						Console.SetCursorPosition (ccol, crow);
+					}
 					needMove = false;
 				}
 				contents [crow, ccol, 0] = (int)(uint)rune;
@@ -102,7 +106,14 @@ namespace Terminal.Gui {
 		public override void End ()
 		{
 			Console.ResetColor ();
-			Console.Clear ();
+			Clear ();
+		}
+
+		void Clear ()
+		{
+			if (Rows > 0) {
+				Console.Clear ();
+			}
 		}
 
 		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
@@ -111,15 +122,16 @@ namespace Terminal.Gui {
 			return new Attribute () { value = ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff) };
 		}
 
-
 		public override void Init (Action terminalResized)
 		{
+			TerminalResized = terminalResized;
+			Console.TreatControlCAsInput = true;
+
 			Colors.TopLevel = new ColorScheme ();
 			Colors.Base = new ColorScheme ();
 			Colors.Dialog = new ColorScheme ();
 			Colors.Menu = new ColorScheme ();
 			Colors.Error = new ColorScheme ();
-			Clip = new Rect (0, 0, Cols, Rows);
 
 			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
 			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
@@ -151,7 +163,15 @@ namespace Terminal.Gui {
 			Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
 			Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
 			Colors.Error.HotFocus = Colors.Error.HotNormal;
-			Console.Clear ();
+			Clear ();
+		}
+
+		void ResizeScreen ()
+		{
+			cols = Console.WindowWidth;
+			rows = Console.WindowHeight;
+			Clip = new Rect (0, 0, Cols, Rows);
+			top = Console.WindowTop;
 		}
 
 		public override Attribute MakeAttribute (Color fore, Color back)
@@ -176,31 +196,14 @@ namespace Terminal.Gui {
 
 		public override void UpdateScreen ()
 		{
-			int rows = Rows;
-			int cols = Cols;
-
-			Console.CursorTop = 0;
-			Console.CursorLeft = 0;
-			for (int row = 0; row < rows; row++) {
-				dirtyLine [row] = false;
-				for (int col = 0; col < cols; col++) {
-					contents [row, col, 2] = 0;
-					var color = contents [row, col, 1];
-					if (color != redrawColor)
-						SetColor (color);
-					Console.Write ((char)contents [row, col, 0]);
-				}
+			if (Rows == 0) {
+				return;
 			}
-		}
 
-		public override void Refresh ()
-		{
 			int rows = Rows;
 			int cols = Cols;
 
-			var savedRow = Console.CursorTop;
-			var savedCol = Console.CursorLeft;
-			for (int row = 0; row < rows; row++) {
+			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row])
 					continue;
 				dirtyLine [row] = false;
@@ -208,8 +211,9 @@ namespace Terminal.Gui {
 					if (contents [row, col, 2] != 1)
 						continue;
 
-					Console.CursorTop = row;
-					Console.CursorLeft = col;
+					if (Console.WindowHeight > 0) {
+						Console.SetCursorPosition (col, row);
+					}
 					for (; col < cols && contents [row, col, 2] == 1; col++) {
 						var color = contents [row, col, 1];
 						if (color != redrawColor)
@@ -220,13 +224,29 @@ namespace Terminal.Gui {
 					}
 				}
 			}
-			Console.CursorTop = savedRow;
-			Console.CursorLeft = savedCol;
+
+			UpdateCursor ();
+		}
+
+		public override void Refresh ()
+		{
+			if (Console.WindowWidth != Cols || Console.WindowHeight != Rows || Console.WindowTop != Top) {
+				ResizeScreen ();
+				UpdateOffscreen ();
+				TerminalResized.Invoke ();
+			}
+
+			UpdateScreen ();
 		}
 
 		public override void UpdateCursor ()
 		{
-			//
+			// Prevents the exception of size changing during resizing.
+			try {
+				if (ccol > 0 && ccol < Console.WindowWidth && crow > 0 && crow < Console.WindowHeight) {
+					Console.SetCursorPosition (ccol, crow);
+				}
+			} catch (ArgumentOutOfRangeException) { }
 		}
 
 		public override void StartReportingMouseMoves ()
@@ -249,6 +269,7 @@ namespace Terminal.Gui {
 
 		Key MapKey (ConsoleKeyInfo keyInfo)
 		{
+			MapKeyModifiers (keyInfo);
 			switch (keyInfo.Key) {
 			case ConsoleKey.Escape:
 				return Key.Esc;
@@ -298,42 +319,75 @@ namespace Terminal.Gui {
 			var key = keyInfo.Key;
 			if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
 				var delta = key - ConsoleKey.A;
-				if (keyInfo.Modifiers == ConsoleModifiers.Control)
-					return (Key)((uint)Key.A + delta);
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt)
+				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
+				}
+				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
-				if (keyInfo.Modifiers == ConsoleModifiers.Shift)
-					return (Key)((uint)Key.A + delta);
-				else
-					return (Key)((uint)'a' + delta);
+				}
+				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+					if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
+						return (Key)((uint)Key.A + delta);
+					}
+				}
+				return (Key)((uint)keyInfo.KeyChar);
 			}
 			if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
 				var delta = key - ConsoleKey.D0;
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt)
+				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 					return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
-				if (keyInfo.Modifiers == ConsoleModifiers.Shift)
-					return (Key)((uint)keyInfo.KeyChar);
-				return (Key)((uint)Key.D0 + delta);
+				}
+				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
+				}
+				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
+						return (Key)((uint)Key.D0 + delta);
+					}
+				}
+				return (Key)((uint)keyInfo.KeyChar);
 			}
-			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F10) {
+			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
 				var delta = key - ConsoleKey.F1;
+				if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+					return (Key)((uint)Key.F1 + delta);
+				}
 
-				return (Key)((int)Key.F1 + delta);
+				return (Key)((uint)Key.F1 + delta);
+			}
+			if (keyInfo.KeyChar != 0) {
+				return (Key)((uint)keyInfo.KeyChar);
 			}
+
 			return (Key)(0xffffffff);
 		}
 
-		KeyModifiers keyModifiers = new KeyModifiers ();
+		KeyModifiers keyModifiers;
+
+		void MapKeyModifiers (ConsoleKeyInfo keyInfo)
+		{
+			if (keyModifiers == null)
+				keyModifiers = new KeyModifiers ();
+
+			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
+				keyModifiers.Shift = true;
+			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
+				keyModifiers.Ctrl = true;
+			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0)
+				keyModifiers.Alt = true;
+		}
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			(mainLoop.Driver as NetMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
 				var map = MapKey (consoleKey);
-				if (map == (Key)0xffffffff)
+				if (map == (Key)0xffffffff) {
 					return;
+				}
 				keyHandler (new KeyEvent (map, keyModifiers));
 				keyUpHandler (new KeyEvent (map, keyModifiers));
+				keyModifiers = null;
 			};
 		}
 
@@ -368,14 +422,17 @@ namespace Terminal.Gui {
 	/// file descriptor monitoring.
 	/// </summary>
 	/// <remarks>
-	/// This implementation is used for both NetDriver and FakeDriver. 
+	/// This implementation is used for NetDriver.
 	/// </remarks>
 	public class NetMainLoop : IMainLoopDriver {
-		AutoResetEvent keyReady = new AutoResetEvent (false);
-		AutoResetEvent waitForProbe = new AutoResetEvent (false);
+		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
+		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
+		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		ConsoleKeyInfo? keyResult = null;
 		MainLoop mainLoop;
-		Func<ConsoleKeyInfo> consoleKeyReaderFn = null;
+		ConsoleDriver consoleDriver;
+		bool winChanged;
+		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 
 		/// <summary>
 		/// Invoked when a Key is pressed.
@@ -383,66 +440,126 @@ namespace Terminal.Gui {
 		public Action<ConsoleKeyInfo> KeyPressed;
 
 		/// <summary>
-		/// Initializes the class.
+		/// Initializes the class with the console driver.
 		/// </summary>
 		/// <remarks>
-		///   Passing a consoleKeyReaderfn is provided to support unit test sceanrios.
+		///   Passing a consoleDriver is provided to capture windows resizing.
 		/// </remarks>
-		/// <param name="consoleKeyReaderFn">The method to be called to get a key from the console.</param>
-		public NetMainLoop (Func<ConsoleKeyInfo> consoleKeyReaderFn = null)
+		/// <param name="consoleDriver">The console driver used by this Net main loop.</param>
+		public NetMainLoop (ConsoleDriver consoleDriver = null)
 		{
-			if (consoleKeyReaderFn == null) {
-				throw new ArgumentNullException ("key reader function must be provided.");
+			if (consoleDriver == null) {
+				throw new ArgumentNullException ("console driver instance must be provided.");
 			}
-			this.consoleKeyReaderFn = consoleKeyReaderFn;
+			this.consoleDriver = consoleDriver;
 		}
 
-		void WindowsKeyReader ()
+		void KeyReader ()
 		{
 			while (true) {
-				waitForProbe.WaitOne ();
-				keyResult = consoleKeyReaderFn();
+				waitForProbe.Wait ();
+				waitForProbe.Reset ();
+				keyResult = Console.ReadKey (true);
 				keyReady.Set ();
 			}
 		}
 
+		void CheckWinChange ()
+		{
+			while (true) {
+				winChange.Wait ();
+				winChange.Reset ();
+				WaitWinChange ();
+				winChanged = true;
+				keyReady.Set ();
+			}
+		}
+
+		void WaitWinChange ()
+		{
+			while (true) {
+				if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows
+					|| Console.WindowTop != consoleDriver.Top) { // Top only working on Windows.
+					return;
+				}
+			}
+		}
+
 		void IMainLoopDriver.Setup (MainLoop mainLoop)
 		{
 			this.mainLoop = mainLoop;
-			Thread readThread = new Thread (WindowsKeyReader);
-			readThread.Start ();
+			Task.Run (KeyReader);
+			Task.Run (CheckWinChange);
 		}
 
 		void IMainLoopDriver.Wakeup ()
 		{
+			keyReady.Set ();
 		}
 
 		bool IMainLoopDriver.EventsPending (bool wait)
 		{
 			long now = DateTime.UtcNow.Ticks;
 
-			int waitTimeout;
+			waitForProbe.Set ();
+			winChange.Set ();
+
+			if (CheckTimers (wait, out var waitTimeout)) {
+				return true;
+			}
+
+			try {
+				if (!tokenSource.IsCancellationRequested) {
+					keyReady.Wait (waitTimeout, tokenSource.Token);
+				}
+			} catch (OperationCanceledException) {
+				return true;
+			} finally {
+				keyReady.Reset ();
+			}
+
+			if (!tokenSource.IsCancellationRequested) {
+				return keyResult.HasValue || CheckTimers (wait, out _) || winChanged;
+			}
+
+			tokenSource.Dispose ();
+			tokenSource = new CancellationTokenSource ();
+			return true;
+		}
+
+		bool CheckTimers (bool wait, out int waitTimeout)
+		{
+			long now = DateTime.UtcNow.Ticks;
+
 			if (mainLoop.timeouts.Count > 0) {
 				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
 				if (waitTimeout < 0)
 					return true;
-			} else
+			} else {
 				waitTimeout = -1;
+			}
 
 			if (!wait)
 				waitTimeout = 0;
 
-			keyResult = null;
-			waitForProbe.Set ();
-			keyReady.WaitOne (waitTimeout);
-			return keyResult.HasValue;
+			int ic;
+			lock (mainLoop.idleHandlers) {
+				ic = mainLoop.idleHandlers.Count;
+			}
+
+			return ic > 0;
 		}
 
 		void IMainLoopDriver.MainIteration ()
 		{
 			if (keyResult.HasValue) {
-				KeyPressed?.Invoke (keyResult.Value);
+				var kr = keyResult;
 				keyResult = null;
+				KeyPressed?.Invoke (kr.Value);
+			}
+			if (winChanged) {
+				winChanged = false;
+				consoleDriver.Refresh ();
 			}
 		}
 	}

+ 1 - 0
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -510,6 +510,7 @@ namespace Terminal.Gui {
 
 		public override int Cols => cols;
 		public override int Rows => rows;
+		public override int Top => 0;
 
 		public WindowsDriver ()
 		{

+ 1 - 1
Terminal.Gui/Core/Application.cs

@@ -190,8 +190,8 @@ namespace Terminal.Gui {
 			if (Driver == null) {
 				var p = Environment.OSVersion.Platform;
 				if (UseSystemConsole) {
-					mainLoopDriver = new NetMainLoop (() => Console.ReadKey (true));
 					Driver = new NetDriver ();
+					mainLoopDriver = new NetMainLoop (Driver);
 				} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
 					var windowsDriver = new WindowsDriver ();
 					mainLoopDriver = windowsDriver;

+ 5 - 0
Terminal.Gui/Core/ConsoleDriver.cs

@@ -541,6 +541,11 @@ namespace Terminal.Gui {
 		/// The current number of rows in the terminal.
 		/// </summary>
 		public abstract int Rows { get; }
+		/// <summary>
+		/// The current top in the terminal.
+		/// </summary>
+		public abstract int Top { get; }
+
 		/// <summary>
 		/// Initializes the driver
 		/// </summary>

+ 4 - 0
Terminal.Gui/Core/View.cs

@@ -1095,6 +1095,10 @@ namespace Terminal.Gui {
 		/// <param name="row">Row.</param>
 		public void Move (int col, int row)
 		{
+			if (Driver.Rows == 0) {
+				return;
+			}
+
 			ViewToScreen (col, row, out var rcol, out var rrow);
 			Driver.Move (rcol, rrow);
 		}

+ 1 - 0
UICatalog/Scenarios/Threading.cs

@@ -127,6 +127,7 @@ namespace UICatalog {
 			await Task.Delay (3000);
 			LogJob ("Returning from task method");
 			await _itemsList.SetSourceAsync (items);
+			_itemsList.SetNeedsDisplay ();
 		}
 
 		private CancellationTokenSource cancellationTokenSource;

+ 2 - 2
UnitTests/ApplicationTests.cs

@@ -28,7 +28,7 @@ namespace Terminal.Gui {
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Current);
 			Assert.NotNull (Application.CurrentView);
 			Assert.NotNull (Application.Top);
@@ -66,7 +66,7 @@ namespace Terminal.Gui {
 
 		void Init ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey(true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.MainLoop);
 		}

+ 3 - 3
UnitTests/ConsoleDriverTests.cs

@@ -11,7 +11,7 @@ namespace Terminal.Gui {
 		public void Init_Inits ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			driver.Init (() => { });
 
 			Assert.Equal (80, Console.BufferWidth);
@@ -27,7 +27,7 @@ namespace Terminal.Gui {
 		public void End_Cleans_Up ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			driver.Init (() => { });
 
 			FakeConsole.ForegroundColor = ConsoleColor.Red;
@@ -50,7 +50,7 @@ namespace Terminal.Gui {
 		public void SetColors_Changes_Colors ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			driver.Init (() => { });
 			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
 			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);

+ 4 - 4
UnitTests/DimTests.cs

@@ -247,7 +247,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Dim_Validation_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -279,7 +279,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -300,7 +300,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -333,7 +333,7 @@ namespace Terminal.Gui {
 		{
 			// Testing with the Button because it properly handles the Dim class.
 
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 

+ 16 - 16
UnitTests/MainLoopTests.cs

@@ -18,7 +18,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Constructor_Setups_Driver ()
 		{
-			var ml = new MainLoop (new NetMainLoop(() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (ml.Driver);
 		}
 
@@ -26,7 +26,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddIdle_Adds_And_Removes ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			Func<bool> fnTrue = () => { return true; };
 			Func<bool> fnFalse = () => { return false; };
@@ -60,7 +60,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddIdle_Function_GetsCalled_OnIteration ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -76,7 +76,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void RemoveIdle_Function_NotCalled ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -93,7 +93,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddThenRemoveIdle_Function_NotCalled ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -111,7 +111,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTwice_Function_CalledTwice ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -139,7 +139,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void False_Idle_Stops_It_Being_Called_Again ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn1 = () => {
@@ -172,7 +172,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddIdle_Twice_Returns_False_Called_Twice ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn1 = () => {
@@ -204,7 +204,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Run_Runs_Idle_Stop_Stops_Idle ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -226,7 +226,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTimer_Adds_Removes_NoFaults ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			var ms = 100;
 
 			var callbackCount = 0;
@@ -246,7 +246,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTimer_Run_Called ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			var ms = 100;
 
 			var callbackCount = 0;
@@ -274,7 +274,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTimer_Run_CalledAtApproximatelyRightTime ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			var ms = TimeSpan.FromMilliseconds (50);
 			var watch = new System.Diagnostics.Stopwatch ();
 
@@ -300,7 +300,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			var ms = TimeSpan.FromMilliseconds (50);
 			var watch = new System.Diagnostics.Stopwatch ();
 
@@ -328,7 +328,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTimer_Remove_NotCalled ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			var ms = TimeSpan.FromMilliseconds (50);
 
 			// Force stop if 10 iterations
@@ -357,7 +357,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void AddTimer_ReturnFalse_StopsBeingCalled ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			var ms = TimeSpan.FromMilliseconds (50);
 
 			// Force stop if 10 iterations
@@ -390,7 +390,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Invoke_Adds_Idle ()
 		{
-			var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var actionCalled = 0;
 			ml.Invoke (() => { actionCalled++; });

+ 4 - 4
UnitTests/PosTests.cs

@@ -262,7 +262,7 @@ namespace Terminal.Gui {
 			// Setup Fake driver
 			(Window win, Button button) setup ()
 			{
-				Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 				Application.Iteration = () => {
 					Application.RequestStop ();
 				};
@@ -374,7 +374,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Pos_Validation_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -406,7 +406,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -428,7 +428,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 

+ 2 - 2
UnitTests/ScenarioTests.cs

@@ -55,7 +55,7 @@ namespace Terminal.Gui {
 						Application.RequestStop ();
 					}
 				};
-				Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 				var ms = 1000;
 				var abortCount = 0;
@@ -107,7 +107,7 @@ namespace Terminal.Gui {
 					Application.RequestStop ();
 				}
 			};
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var ms = 1000;
 			var abortCount = 0;

+ 8 - 8
UnitTests/ViewTests.cs

@@ -544,7 +544,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Initialized_Event_Comparing_With_Added_Event ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = new Toplevel () { Id = "0", };
 
@@ -643,7 +643,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Initialized_Event_Will_Be_Invoked_When_Added_Dynamically ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = new Toplevel () { Id = "0", };
 
@@ -751,7 +751,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void CanFocus_Faced_With_Container_Before_Run ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -788,7 +788,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void CanFocus_Faced_With_Container_After_Run ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -831,7 +831,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void CanFocus_Container_ToFalse_Turns_All_Subviews_ToFalse_Too ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -866,7 +866,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void CanFocus_Container_Toggling_All_Subviews_To_Old_Value_When_Is_True ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 
@@ -910,7 +910,7 @@ namespace Terminal.Gui {
 		{
 			// Non-regression test for #882 (NullReferenceException during keyboard navigation when Focused is null)
 
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			Application.Top.Ready += () => {
 				Assert.Null (Application.Top.Focused);
@@ -928,7 +928,7 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Multi_Thread_Toplevels ()
 		{
-			Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 			var t = Application.Top;
 			var w = new Window ();