From d0e187585e8b520b7075fb26c90d1437f31b179e Mon Sep 17 00:00:00 2001 From: miguel Date: Tue, 15 May 2018 22:48:05 -0400 Subject: [PATCH] Refactor the windows driver to avoid the races --- .editorconfig | 2 +- Terminal.Gui/Core.cs | 21 +- Terminal.Gui/Drivers/CursesDriver.cs | 2 +- Terminal.Gui/Drivers/NetDriver.cs | 2 +- Terminal.Gui/Drivers/WindowsDriver.cs | 201 +++++++---- Terminal.Gui/MonoCurses/mainloop.cs | 469 +++++++++++++------------- 6 files changed, 380 insertions(+), 317 deletions(-) diff --git a/.editorconfig b/.editorconfig index f7a1aa561..eb2cc0cea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,7 @@ [*.cs] indent_style = tab tab_width = 8 -csharp_new_line_before_open_brace = true +csharp_new_line_before_open_brace = methods,local_functions csharp_new_line_before_else = false csharp_new_line_before_catch = false csharp_new_line_before_finally = false diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 6d78e7da1..bbb021763 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1591,20 +1591,21 @@ namespace Terminal.Gui { return; var p = Environment.OSVersion.Platform; + Mono.Terminal.IMainLoopDriver mainLoopDriver; - if (UseSystemConsole) + if (UseSystemConsole) { + mainLoopDriver = new Mono.Terminal.NetMainLoop (); Driver = new NetDriver (); - // - // The driver currently has a race and does not integrate into mainloop, so input - // only works at random. I need to change the code to do proper polling on mainloop - // and then delegate the reading of events to WindowsConsole - // - //else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) - // Driver = new WindowsDriver(); - else + } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows){ + var windowsDriver = new WindowsDriver (); + mainLoopDriver = windowsDriver; + Driver = windowsDriver; + } else { + mainLoopDriver = new Mono.Terminal.UnixMainLoop (); Driver = new CursesDriver (); + } Driver.Init (TerminalResized); - MainLoop = new Mono.Terminal.MainLoop (Driver is CursesDriver, UseSystemConsole); + MainLoop = new Mono.Terminal.MainLoop (mainLoopDriver); SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop)); Top = Toplevel.Create (); Current = Top; diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs index 1f3b94508..1e10cf2f5 100644 --- a/Terminal.Gui/Drivers/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver.cs @@ -196,7 +196,7 @@ namespace Terminal.Gui { { Curses.timeout (-1); - mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => { + (mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => { ProcessInput (keyHandler, mouseHandler); return true; }); diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs index 62e0ef53f..fa24d1969 100644 --- a/Terminal.Gui/Drivers/NetDriver.cs +++ b/Terminal.Gui/Drivers/NetDriver.cs @@ -316,7 +316,7 @@ namespace Terminal.Gui { public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler) { - mainLoop.WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) { + (mainLoop.Driver as NetMainLoop).WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) { var map = MapKey (consoleKey); if (map == (Key)0xffffffff) return; diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 229931a48..1b9084f52 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -27,6 +27,7 @@ // using System; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Mono.Terminal; using NStack; @@ -38,14 +39,19 @@ namespace Terminal.Gui { public const int STD_INPUT_HANDLE = -10; public const int STD_ERROR_HANDLE = -12; - IntPtr InputHandle, OutputHandle; - + internal IntPtr InputHandle, OutputHandle; IntPtr ScreenBuffer; + uint originalConsoleMode; public WindowsConsole () { InputHandle = GetStdHandle (STD_INPUT_HANDLE); OutputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + originalConsoleMode = ConsoleMode; + var newConsoleMode = originalConsoleMode; + newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags); + newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; + ConsoleMode = newConsoleMode; } public CharInfo[] OriginalStdOutChars; @@ -86,37 +92,9 @@ namespace Terminal.Gui { return SetConsoleCursorPosition (ScreenBuffer, position); } - public void PollEvents (Action inputEventHandler) - { - if (OriginalConsoleMode != 0) - return; - - OriginalConsoleMode = ConsoleMode; - - ConsoleMode |= (uint)ConsoleModes.EnableMouseInput; - ConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; - ConsoleMode |= (uint)ConsoleModes.EnableExtendedFlags; - - Task.Run (() => - { - uint numberEventsRead = 0; - uint length = 1; - InputRecord[] records = new InputRecord[length]; - - while (ContinueListeningForConsoleEvents && - ReadConsoleInput(InputHandle, records, length, out numberEventsRead) && - numberEventsRead > 0){ - inputEventHandler (records[0]); - } - }); - } - public void Cleanup () { ContinueListeningForConsoleEvents = false; - ConsoleMode = OriginalConsoleMode; - OriginalConsoleMode = 0; - if (!SetConsoleActiveScreenBuffer (OutputHandle)){ var err = Marshal.GetLastWin32Error (); Console.WriteLine("Error: {0}", err); @@ -125,8 +103,6 @@ namespace Terminal.Gui { private bool ContinueListeningForConsoleEvents = true; - private uint OriginalConsoleMode = 0; - public uint ConsoleMode { get { uint v; @@ -401,31 +377,132 @@ namespace Terminal.Gui { [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetConsoleActiveScreenBuffer(IntPtr Handle); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool GetNumberOfConsoleInputEvents (IntPtr handle, out uint lpcNumberOfEvents); + public uint InputEventCount { + get { + uint v; + GetNumberOfConsoleInputEvents (InputHandle, out v); + return v; + } + } } - internal class WindowsDriver : ConsoleDriver { - + internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver { + static bool sync; + AutoResetEvent eventReady = new AutoResetEvent (false); + AutoResetEvent waitForProbe = new AutoResetEvent (false); + MainLoop mainLoop; Action TerminalResized; - - WindowsConsole WinConsole; - WindowsConsole.CharInfo[] OutputBuffer; - int cols, rows; + WindowsConsole winConsole; public override int Cols => cols; - public override int Rows => rows; - static bool sync; - public WindowsDriver () { - WinConsole = new WindowsConsole(); + winConsole = new WindowsConsole(); + cols = Console.WindowWidth; rows = Console.WindowHeight - 1; + ResizeScreen (); UpdateOffScreen (); + + Task.Run ((Action)WindowsInputHandler); + } + + // The records that we keep fetching + WindowsConsole.InputRecord [] result, records = new WindowsConsole.InputRecord [1]; + + void WindowsInputHandler () + { + while (true) { + waitForProbe.WaitOne (); + + uint numberEventsRead = 0; + + WindowsConsole.ReadConsoleInput (winConsole.InputHandle, records, 1, out numberEventsRead); + if (numberEventsRead == 0) + result = null; + else + result = records; + + eventReady.Set (); + } + } + + void IMainLoopDriver.Setup (MainLoop mainLoop) + { + this.mainLoop = mainLoop; + } + + 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; + + result = null; + waitForProbe.Set (); + eventReady.WaitOne (waitTimeout); + return result != null; + } + + Action keyHandler; + Action mouseHandler; + + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler) + { + this.keyHandler = keyHandler; + this.mouseHandler = mouseHandler; + } + + + void IMainLoopDriver.MainIteration () + { + if (result == null) + return; + + var inputEvent = result [0]; + switch (inputEvent.EventType) { + case WindowsConsole.EventType.Key: + if (inputEvent.KeyEvent.bKeyDown == false) + return; + var map = MapKey (ToConsoleKeyInfo (inputEvent.KeyEvent)); + if (map == (Key)0xffffffff) + return; + keyHandler (new KeyEvent (map)); + break; + + case WindowsConsole.EventType.Mouse: + mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); + break; + + case WindowsConsole.EventType.WindowBufferSize: + cols = inputEvent.WindowBufferSizeEvent.size.X; + rows = inputEvent.WindowBufferSizeEvent.size.Y - 1; + ResizeScreen (); + UpdateOffScreen (); + TerminalResized (); + break; + } + result = null; } private WindowsConsole.ButtonState? LastMouseButtonPressed = null; @@ -572,35 +649,6 @@ namespace Terminal.Gui { return (Key)(0xffffffff); } - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler) - { - WinConsole.PollEvents (inputEvent => - { - switch(inputEvent.EventType){ - case WindowsConsole.EventType.Key: - if (inputEvent.KeyEvent.bKeyDown == false) - return; - var map = MapKey (ToConsoleKeyInfo (inputEvent.KeyEvent)); - if (map == (Key) 0xffffffff) - return; - keyHandler (new KeyEvent (map)); - break; - - case WindowsConsole.EventType.Mouse: - mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); - break; - - case WindowsConsole.EventType.WindowBufferSize: - cols = inputEvent.WindowBufferSizeEvent.size.X; - rows = inputEvent.WindowBufferSizeEvent.size.Y - 1; - ResizeScreen (); - UpdateOffScreen (); - TerminalResized (); - break; - } - }); - } - public override void Init (Action terminalResized) { TerminalResized = terminalResized; @@ -722,7 +770,7 @@ namespace Terminal.Gui { }; UpdateCursor(); - WinConsole.WriteToConsole (OutputBuffer, bufferCoords, window); + winConsole.WriteToConsole (OutputBuffer, bufferCoords, window); } public override void UpdateScreen () @@ -740,7 +788,7 @@ namespace Terminal.Gui { }; UpdateCursor(); - WinConsole.WriteToConsole (OutputBuffer, bufferCoords, window); + winConsole.WriteToConsole (OutputBuffer, bufferCoords, window); } public override void UpdateCursor() @@ -749,11 +797,11 @@ namespace Terminal.Gui { X = (short)ccol, Y = (short)crow }; - WinConsole.SetCursorPosition(position); + winConsole.SetCursorPosition(position); } public override void End () { - WinConsole.Cleanup(); + winConsole.Cleanup(); } #region Unused @@ -785,5 +833,8 @@ namespace Terminal.Gui { { } #endregion + } + + } diff --git a/Terminal.Gui/MonoCurses/mainloop.cs b/Terminal.Gui/MonoCurses/mainloop.cs index e44566694..8a7bc5778 100644 --- a/Terminal.Gui/MonoCurses/mainloop.cs +++ b/Terminal.Gui/MonoCurses/mainloop.cs @@ -32,243 +32,44 @@ using System.Threading; namespace Mono.Terminal { + /// + /// Public interface to create your own platform specific main loop driver. + /// public interface IMainLoopDriver { + /// + /// Initializes the main loop driver, gets the calling main loop for the initialization. + /// + /// Main loop. void Setup (MainLoop mainLoop); + + /// + /// Wakes up the mainloop that might be waiting on input, must be thread safe. + /// void Wakeup (); + + /// + /// Must report whether there are any events pending, or even block waiting for events. + /// + /// true, if there were pending events, false otherwise. + /// If set to true wait until an event is available, otherwise return immediately. bool EventsPending (bool wait); void MainIteration (); } - internal class UnixMainLoop : IMainLoopDriver { + /// + /// Unix main loop, suitable for using on Posix systems + /// + /// + /// In addition to the general functions of the mainloop, the Unix version + /// can watch file descriptors using the AddWatch methods. + /// + public class UnixMainLoop : IMainLoopDriver { [StructLayout (LayoutKind.Sequential)] struct Pollfd { public int fd; public short events, revents; } - class Watch { - public int File; - public Condition Condition; - public Func Callback; - } - - Dictionary descriptorWatchers = new Dictionary (); - - [DllImport ("libc")] - extern static int poll ([In, Out]Pollfd [] ufds, uint nfds, int timeout); - - [DllImport ("libc")] - extern static int pipe ([In, Out]int [] pipes); - - [DllImport ("libc")] - extern static int read (int fd, IntPtr buf, IntPtr n); - - [DllImport ("libc")] - extern static int write (int fd, IntPtr buf, IntPtr n); - - Pollfd [] pollmap; - bool poll_dirty = true; - int [] wakeupPipes = new int [2]; - static IntPtr ignore = Marshal.AllocHGlobal (1); - - void IMainLoopDriver.Wakeup () - { - write (wakeupPipes [1], ignore, (IntPtr) 1); - } - - void IMainLoopDriver.Setup (MainLoop mainLoop) { - pipe (wakeupPipes); - mainLoop.AddWatch (wakeupPipes [0], MainLoop.Condition.PollIn, ml => { - read (wakeupPipes [0], ignore, (IntPtr)1); - return true; - }); - } - - /// - /// Removes an active watch from the mainloop. - /// - /// - /// The token parameter is the value returned from AddWatch - /// - public void RemoveWatch (object token) - { - var watch = token as Watch; - if (watch == null) - return; - descriptorWatchers.Remove (watch.File); - } - - /// - /// Watches a file descriptor for activity. - /// - /// - /// When the condition is met, the provided callback - /// is invoked. If the callback returns false, the - /// watch is automatically removed. - /// - /// The return value is a token that represents this watch, you can - /// use this token to remove the watch by calling RemoveWatch. - /// - public object AddWatch (int fileDescriptor, MainLoop.Condition condition, Func callback) - { - if (callback == null) - throw new ArgumentNullException (nameof(callback)); - - var watch = new Watch () { Condition = condition, Callback = callback, File = fileDescriptor }; - descriptorWatchers [fileDescriptor] = watch; - poll_dirty = true; - return watch; - } - - void UpdatePollMap () - { - if (!poll_dirty) - return; - poll_dirty = false; - - pollmap = new Pollfd [descriptorWatchers.Count]; - int i = 0; - foreach (var fd in descriptorWatchers.Keys) { - pollmap [i].fd = fd; - pollmap [i].events = (short)descriptorWatchers [fd].Condition; - i++; - } - } - - bool IMainLoopDriver.EventsPending (bool wait) - { - long now = DateTime.UtcNow.Ticks; - - int pollTimeout, n; - if (timeouts.Count > 0) { - pollTimeout = (int)((timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); - if (pollTimeout < 0) - return true; - - } else - pollTimeout = -1; - - if (!wait) - pollTimeout = 0; - - UpdatePollMap (); - - n = poll (pollmap, (uint)pollmap.Length, pollTimeout); - int ic; - lock (idleHandlers) - ic = idleHandlers.Count; - return n > 0 || timeouts.Count > 0 && ((timeouts.Keys [0] - DateTime.UtcNow.Ticks) < 0) || ic > 0; - } - - void IMainLoopDriver.MainIteration () - { - if (pollmap != null) { - foreach (var p in pollmap) { - Watch watch; - - if (p.revents == 0) - continue; - - if (!descriptorWatchers.TryGetValue (p.fd, out watch)) - continue; - if (!watch.Callback (this)) - descriptorWatchers.Remove (p.fd); - } - } - } - } - - internal class NetMainLoop : IMainLoopDriver { - AutoResetEvent keyReady = new AutoResetEvent (false); - AutoResetEvent waitForProbe = new AutoResetEvent (false); - ConsoleKeyInfo? windowsKeyResult = null; - Action keyCallback; - - public NetMainLoop (Action keyCallback) - { - this.keyCallback = keyCallback; - } - - void WindowsKeyReader () - { - while (true) { - waitForProbe.WaitOne (); - windowsKeyResult = Console.ReadKey (true); - keyReady.Set (); - } - } - - void IMainLoopDriver.Setup (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 (timeouts.Count > 0) { - waitTimeout = (int)((timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); - if (waitTimeout < 0) - return true; - } else - waitTimeout = -1; - - if (!wait) - waitTimeout = 0; - - windowsKeyResult = null; - waitForProbe.Set (); - keyReady.WaitOne (waitTimeout); - return windowsKeyResult.HasValue; - } - - void IMainLoopDriver.MainIteration () - { - if (windowsKeyResult.HasValue) { - if (keyCallback != null) - keyCallback (windowsKeyResult.Value); - windowsKeyResult = null; - } - } - } - - internal class WinConsoleLoop : IMainLoopDriver { - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - } - - void IMainLoopDriver.Wakeup () - { - } - - bool IMainLoopDriver.EventsPending (bool wait) - { - return false; - } - - void IMainLoopDriver.MainIteration () - { - } - } - - /// - /// Simple main loop implementation that can be used to monitor - /// file descriptor, run timers and idle handlers. - /// - /// - /// Monitoring of file descriptors is only available on Unix, there - /// does not seem to be a way of supporting this on Windows. - /// - public class MainLoop { - bool useUnix = true; - /// /// Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions. /// @@ -300,20 +101,230 @@ namespace Mono.Terminal { PollNval = 32 } + class Watch { + public int File; + public Condition Condition; + public Func Callback; + } - class Timeout { + Dictionary descriptorWatchers = new Dictionary (); + + [DllImport ("libc")] + extern static int poll ([In, Out]Pollfd [] ufds, uint nfds, int timeout); + + [DllImport ("libc")] + extern static int pipe ([In, Out]int [] pipes); + + [DllImport ("libc")] + extern static int read (int fd, IntPtr buf, IntPtr n); + + [DllImport ("libc")] + extern static int write (int fd, IntPtr buf, IntPtr n); + + Pollfd [] pollmap; + bool poll_dirty = true; + int [] wakeupPipes = new int [2]; + static IntPtr ignore = Marshal.AllocHGlobal (1); + MainLoop mainLoop; + + void IMainLoopDriver.Wakeup () + { + write (wakeupPipes [1], ignore, (IntPtr) 1); + } + + void IMainLoopDriver.Setup (MainLoop mainLoop) { + this.mainLoop = mainLoop; + pipe (wakeupPipes); + AddWatch (wakeupPipes [0], Condition.PollIn, ml => { + read (wakeupPipes [0], ignore, (IntPtr)1); + return true; + }); + } + + /// + /// Removes an active watch from the mainloop. + /// + /// + /// The token parameter is the value returned from AddWatch + /// + public void RemoveWatch (object token) + { + var watch = token as Watch; + if (watch == null) + return; + descriptorWatchers.Remove (watch.File); + } + + /// + /// Watches a file descriptor for activity. + /// + /// + /// When the condition is met, the provided callback + /// is invoked. If the callback returns false, the + /// watch is automatically removed. + /// + /// The return value is a token that represents this watch, you can + /// use this token to remove the watch by calling RemoveWatch. + /// + public object AddWatch (int fileDescriptor, Condition condition, Func callback) + { + if (callback == null) + throw new ArgumentNullException (nameof(callback)); + + var watch = new Watch () { Condition = condition, Callback = callback, File = fileDescriptor }; + descriptorWatchers [fileDescriptor] = watch; + poll_dirty = true; + return watch; + } + + void UpdatePollMap () + { + if (!poll_dirty) + return; + poll_dirty = false; + + pollmap = new Pollfd [descriptorWatchers.Count]; + int i = 0; + foreach (var fd in descriptorWatchers.Keys) { + pollmap [i].fd = fd; + pollmap [i].events = (short)descriptorWatchers [fd].Condition; + i++; + } + } + + bool IMainLoopDriver.EventsPending (bool wait) + { + long now = DateTime.UtcNow.Ticks; + + int pollTimeout, n; + if (mainLoop.timeouts.Count > 0) { + pollTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); + if (pollTimeout < 0) + return true; + + } else + pollTimeout = -1; + + if (!wait) + pollTimeout = 0; + + UpdatePollMap (); + + n = poll (pollmap, (uint)pollmap.Length, pollTimeout); + int ic; + lock (mainLoop.idleHandlers) + ic = mainLoop.idleHandlers.Count; + return n > 0 || mainLoop.timeouts.Count > 0 && ((mainLoop.timeouts.Keys [0] - DateTime.UtcNow.Ticks) < 0) || ic > 0; + } + + void IMainLoopDriver.MainIteration () + { + if (pollmap != null) { + foreach (var p in pollmap) { + Watch watch; + + if (p.revents == 0) + continue; + + if (!descriptorWatchers.TryGetValue (p.fd, out watch)) + continue; + if (!watch.Callback (this.mainLoop)) + descriptorWatchers.Remove (p.fd); + } + } + } + } + + /// + /// 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. + /// + class NetMainLoop : IMainLoopDriver { + AutoResetEvent keyReady = new AutoResetEvent (false); + AutoResetEvent waitForProbe = new AutoResetEvent (false); + ConsoleKeyInfo? windowsKeyResult = null; + public Action WindowsKeyPressed; + MainLoop mainLoop; + + public NetMainLoop () + { + } + + void WindowsKeyReader () + { + while (true) { + waitForProbe.WaitOne (); + windowsKeyResult = Console.ReadKey (true); + 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; + + windowsKeyResult = null; + waitForProbe.Set (); + keyReady.WaitOne (waitTimeout); + return windowsKeyResult.HasValue; + } + + void IMainLoopDriver.MainIteration () + { + if (windowsKeyResult.HasValue) { + if (WindowsKeyPressed!= null) + WindowsKeyPressed (windowsKeyResult.Value); + windowsKeyResult = null; + } + } + } + + /// + /// Simple main loop implementation that can be used to monitor + /// file descriptor, run timers and idle handlers. + /// + /// + /// Monitoring of file descriptors is only available on Unix, there + /// does not seem to be a way of supporting this on Windows. + /// + public class MainLoop { + internal class Timeout { public TimeSpan Span; public Func Callback; } - SortedList timeouts = new SortedList (); - List> idleHandlers = new List> (); + internal SortedList timeouts = new SortedList (); + internal List> idleHandlers = new List> (); IMainLoopDriver driver; - IMainLoopDriver Driver => driver; + public IMainLoopDriver Driver => driver; /// - /// Default constructor + /// Creates a new Mainloop, to run it you must provide a driver, and choose + /// one of the implementations UnixMainLoop, NetMainLoop or WindowsMainLoop. /// public MainLoop (IMainLoopDriver driver) {