|
@@ -3,7 +3,6 @@
|
|
|
// mainloop.cs: Linux/Curses MainLoop implementation.
|
|
|
//
|
|
|
|
|
|
-using System.Collections.Concurrent;
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
namespace Terminal.Gui;
|
|
@@ -13,7 +12,7 @@ namespace Terminal.Gui;
|
|
|
/// In addition to the general functions of the MainLoop, the Unix version can watch file descriptors using the
|
|
|
/// AddWatch methods.
|
|
|
/// </remarks>
|
|
|
-internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
|
|
|
+internal class UnixMainLoop : IMainLoopDriver
|
|
|
{
|
|
|
/// <summary>Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions.</summary>
|
|
|
[Flags]
|
|
@@ -38,17 +37,31 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
|
|
|
PollNval = 32
|
|
|
}
|
|
|
|
|
|
- private readonly CursesDriver _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
|
|
|
+ public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff);
|
|
|
+ private static readonly nint _ignore = Marshal.AllocHGlobal (1);
|
|
|
+
|
|
|
+ private readonly CursesDriver _cursesDriver;
|
|
|
+ private readonly Dictionary<int, Watch> _descriptorWatchers = new ();
|
|
|
+ private readonly int [] _wakeUpPipes = new int [2];
|
|
|
private MainLoop? _mainLoop;
|
|
|
+ private bool _pollDirty = true;
|
|
|
private Pollfd []? _pollMap;
|
|
|
- private readonly ConcurrentQueue<PollData> _pollDataQueue = new ();
|
|
|
- private readonly ManualResetEventSlim _eventReady = new (false);
|
|
|
- ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false);
|
|
|
- private readonly ManualResetEventSlim _windowSizeChange = new (false);
|
|
|
- private readonly CancellationTokenSource _eventReadyTokenSource = new ();
|
|
|
- private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
|
|
|
+ private bool _winChanged;
|
|
|
+
|
|
|
+ public UnixMainLoop (ConsoleDriver consoleDriver)
|
|
|
+ {
|
|
|
+ ArgumentNullException.ThrowIfNull (consoleDriver);
|
|
|
+
|
|
|
+ _cursesDriver = (CursesDriver)consoleDriver;
|
|
|
+ }
|
|
|
|
|
|
- void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
|
|
|
+ void IMainLoopDriver.Wakeup ()
|
|
|
+ {
|
|
|
+ if (!ConsoleDriver.RunningUnitTests)
|
|
|
+ {
|
|
|
+ write (_wakeUpPipes [1], _ignore, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
void IMainLoopDriver.Setup (MainLoop mainLoop)
|
|
|
{
|
|
@@ -61,358 +74,143 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- // Setup poll for stdin (fd 0)
|
|
|
- _pollMap = new Pollfd [1];
|
|
|
- _pollMap [0].fd = 0; // stdin (file descriptor 0)
|
|
|
- _pollMap [0].events = (short)Condition.PollIn; // Monitor input for reading
|
|
|
+ pipe (_wakeUpPipes);
|
|
|
+
|
|
|
+ AddWatch (
|
|
|
+ _wakeUpPipes [0],
|
|
|
+ Condition.PollIn,
|
|
|
+ _ =>
|
|
|
+ {
|
|
|
+ read (_wakeUpPipes [0], _ignore, 1);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ );
|
|
|
}
|
|
|
catch (DllNotFoundException e)
|
|
|
{
|
|
|
throw new NotSupportedException ("libc not found", e);
|
|
|
}
|
|
|
-
|
|
|
- AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed;
|
|
|
-
|
|
|
- Task.Run (CursesInputHandler, _inputHandlerTokenSource.Token);
|
|
|
- Task.Run (WindowSizeHandler, _inputHandlerTokenSource.Token);
|
|
|
}
|
|
|
|
|
|
- private static readonly int TIOCGWINSZ = GetTIOCGWINSZValue ();
|
|
|
-
|
|
|
- private const string PlaceholderLibrary = "compiled-binaries/libGetTIOCGWINSZ"; // Placeholder, won't directly load
|
|
|
-
|
|
|
- [DllImport (PlaceholderLibrary, EntryPoint = "get_tiocgwinsz_value")]
|
|
|
- private static extern int GetTIOCGWINSZValueInternal ();
|
|
|
-
|
|
|
- public static int GetTIOCGWINSZValue ()
|
|
|
+ bool IMainLoopDriver.EventsPending ()
|
|
|
{
|
|
|
- // Determine the correct library path based on the OS
|
|
|
- string libraryPath = Path.Combine (
|
|
|
- AppContext.BaseDirectory,
|
|
|
- "compiled-binaries",
|
|
|
- RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ? "libGetTIOCGWINSZ.dylib" : "libGetTIOCGWINSZ.so");
|
|
|
-
|
|
|
- // Load the native library manually
|
|
|
- nint handle = NativeLibrary.Load (libraryPath);
|
|
|
-
|
|
|
- // Ensure the handle is valid
|
|
|
- if (handle == nint.Zero)
|
|
|
+ if (ConsoleDriver.RunningUnitTests)
|
|
|
{
|
|
|
- throw new DllNotFoundException ($"Unable to load library: {libraryPath}");
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- return GetTIOCGWINSZValueInternal ();
|
|
|
- }
|
|
|
+ UpdatePollMap ();
|
|
|
|
|
|
- private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e)
|
|
|
- {
|
|
|
- _pollDataQueue.Enqueue (EnqueueMouseEvent (e.Flags, e.Position));
|
|
|
- }
|
|
|
+ bool checkTimersResult = _mainLoop!.CheckTimersAndIdleHandlers (out int pollTimeout);
|
|
|
|
|
|
- private void WindowSizeHandler ()
|
|
|
- {
|
|
|
- var ws = new Winsize ();
|
|
|
- ioctl (0, TIOCGWINSZ, ref ws);
|
|
|
-
|
|
|
- // Store initial window size
|
|
|
- int rows = ws.ws_row;
|
|
|
- int cols = ws.ws_col;
|
|
|
+ int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout);
|
|
|
|
|
|
- while (_inputHandlerTokenSource is { IsCancellationRequested: false })
|
|
|
+ if (n == KEY_RESIZE)
|
|
|
{
|
|
|
- try
|
|
|
- {
|
|
|
- _windowSizeChange.Wait (_inputHandlerTokenSource.Token);
|
|
|
- _windowSizeChange.Reset ();
|
|
|
-
|
|
|
- while (!_inputHandlerTokenSource.IsCancellationRequested)
|
|
|
- {
|
|
|
- // Wait for a while then check if screen has changed sizes
|
|
|
- Task.Delay (500, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
|
|
|
-
|
|
|
- ioctl (0, TIOCGWINSZ, ref ws);
|
|
|
-
|
|
|
- if (rows != ws.ws_row || cols != ws.ws_col)
|
|
|
- {
|
|
|
- rows = ws.ws_row;
|
|
|
- cols = ws.ws_col;
|
|
|
-
|
|
|
- _pollDataQueue.Enqueue (EnqueueWindowSizeEvent (rows, cols));
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- _eventReady.Set ();
|
|
|
+ _winChanged = true;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- bool IMainLoopDriver.ForceRead { get; set; }
|
|
|
- private int _retries;
|
|
|
|
|
|
- private void CursesInputHandler ()
|
|
|
- {
|
|
|
- while (_mainLoop is { })
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- if (ex is OperationCanceledException or ObjectDisposedException)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- throw;
|
|
|
- }
|
|
|
-
|
|
|
- ((IMainLoopDriver)this).WaitForInput.Reset ();
|
|
|
- }
|
|
|
-
|
|
|
- ProcessInputQueue ();
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
+ return checkTimersResult || n >= KEY_RESIZE;
|
|
|
}
|
|
|
|
|
|
- private void ProcessInputQueue ()
|
|
|
+ void IMainLoopDriver.Iteration ()
|
|
|
{
|
|
|
- if (_pollDataQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead)
|
|
|
+ if (ConsoleDriver.RunningUnitTests)
|
|
|
{
|
|
|
- while (!_inputHandlerTokenSource.IsCancellationRequested)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- int n = poll (_pollMap!, (uint)_pollMap!.Length, 0);
|
|
|
-
|
|
|
- if (n > 0)
|
|
|
- {
|
|
|
- // Check if stdin has data
|
|
|
- if ((_pollMap [0].revents & (int)Condition.PollIn) != 0)
|
|
|
- {
|
|
|
- // Allocate memory for the buffer
|
|
|
- var buf = new byte [2048];
|
|
|
- nint bufPtr = Marshal.AllocHGlobal (buf.Length);
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- // Read from the stdin
|
|
|
- int bytesRead = read (_pollMap [0].fd, bufPtr, buf.Length);
|
|
|
-
|
|
|
- if (bytesRead > 0)
|
|
|
- {
|
|
|
- // Copy the data from unmanaged memory to a byte array
|
|
|
- var buffer = new byte [bytesRead];
|
|
|
- Marshal.Copy (bufPtr, buffer, 0, bytesRead);
|
|
|
-
|
|
|
- // Convert the byte array to a string (assuming UTF-8 encoding)
|
|
|
- string data = Encoding.UTF8.GetString (buffer);
|
|
|
-
|
|
|
- if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { })
|
|
|
- {
|
|
|
- data = data.Insert (0, AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos));
|
|
|
- AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null;
|
|
|
- }
|
|
|
-
|
|
|
- // Enqueue the data
|
|
|
- ProcessEnqueuePollData (data);
|
|
|
- }
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- // Free the allocated memory
|
|
|
- Marshal.FreeHGlobal (bufPtr);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (_retries > 0)
|
|
|
- {
|
|
|
- _retries = 0;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
|
|
|
- {
|
|
|
- if (_retries > 1)
|
|
|
- {
|
|
|
- if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus))
|
|
|
- {
|
|
|
- lock (seqReqStatus.AnsiRequest._responseLock)
|
|
|
- {
|
|
|
- AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
|
|
|
-
|
|
|
- seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- _retries = 0;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _retries++;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _retries = 0;
|
|
|
- }
|
|
|
- }
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- if (_pollDataQueue.Count > 0)
|
|
|
+ if (_winChanged)
|
|
|
{
|
|
|
- _eventReady.Set ();
|
|
|
+ _winChanged = false;
|
|
|
+ _cursesDriver.ProcessInput ();
|
|
|
+
|
|
|
+ // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426
|
|
|
+ _cursesDriver.ProcessWinChange ();
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private void ProcessEnqueuePollData (string pollData)
|
|
|
- {
|
|
|
- foreach (string split in AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (pollData))
|
|
|
+ if (_pollMap is null)
|
|
|
{
|
|
|
- EnqueuePollData (split);
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private void EnqueuePollData (string pollDataPart)
|
|
|
- {
|
|
|
- ConsoleKeyInfo [] cki = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (pollDataPart);
|
|
|
-
|
|
|
- ConsoleKey key = 0;
|
|
|
- ConsoleModifiers mod = 0;
|
|
|
- ConsoleKeyInfo newConsoleKeyInfo = default;
|
|
|
-
|
|
|
- AnsiEscapeSequenceRequestUtils.DecodeEscSeq (
|
|
|
- ref newConsoleKeyInfo,
|
|
|
- ref key,
|
|
|
- cki,
|
|
|
- ref mod,
|
|
|
- out string c1Control,
|
|
|
- out string code,
|
|
|
- out string [] values,
|
|
|
- out string terminating,
|
|
|
- out bool isMouse,
|
|
|
- out List<MouseFlags> mouseFlags,
|
|
|
- out Point pos,
|
|
|
- out AnsiEscapeSequenceRequestStatus? seqReqStatus,
|
|
|
- AnsiEscapeSequenceRequestUtils.ProcessMouseEvent
|
|
|
- );
|
|
|
-
|
|
|
- if (isMouse)
|
|
|
+ foreach (Pollfd p in _pollMap)
|
|
|
{
|
|
|
- foreach (MouseFlags mf in mouseFlags)
|
|
|
+ if (p.revents == 0)
|
|
|
{
|
|
|
- _pollDataQueue.Enqueue (EnqueueMouseEvent (mf, pos));
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!_descriptorWatchers.TryGetValue (p.fd, out Watch? watch))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- if (newConsoleKeyInfo != default)
|
|
|
- {
|
|
|
- _pollDataQueue.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo));
|
|
|
+ if (!watch.Callback (_mainLoop!))
|
|
|
+ {
|
|
|
+ _descriptorWatchers.Remove (p.fd);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private PollData EnqueueMouseEvent (MouseFlags mouseFlags, Point pos)
|
|
|
+ void IMainLoopDriver.TearDown ()
|
|
|
{
|
|
|
- var mouseEvent = new MouseEvent { Position = pos, MouseFlags = mouseFlags };
|
|
|
+ _descriptorWatchers.Clear ();
|
|
|
|
|
|
- return new () { EventType = EventType.Mouse, MouseEvent = mouseEvent };
|
|
|
+ _mainLoop = null;
|
|
|
}
|
|
|
|
|
|
- private PollData EnqueueKeyboardEvent (ConsoleKeyInfo keyInfo)
|
|
|
+ /// <summary>Watches a file descriptor for activity.</summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// 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.
|
|
|
+ /// </remarks>
|
|
|
+ internal object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop, bool> callback)
|
|
|
{
|
|
|
- return new () { EventType = EventType.Key, KeyEvent = keyInfo };
|
|
|
- }
|
|
|
+ ArgumentNullException.ThrowIfNull (callback);
|
|
|
|
|
|
- private PollData EnqueueWindowSizeEvent (int rows, int cols)
|
|
|
- {
|
|
|
- return new () { EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (cols, rows) } };
|
|
|
+ var watch = new Watch { Condition = condition, Callback = callback, File = fileDescriptor };
|
|
|
+ _descriptorWatchers [fileDescriptor] = watch;
|
|
|
+ _pollDirty = true;
|
|
|
+
|
|
|
+ return watch;
|
|
|
}
|
|
|
|
|
|
- bool IMainLoopDriver.EventsPending ()
|
|
|
+ /// <summary>Removes an active watch from the mainloop.</summary>
|
|
|
+ /// <remarks>The token parameter is the value returned from AddWatch</remarks>
|
|
|
+ internal void RemoveWatch (object token)
|
|
|
{
|
|
|
- ((IMainLoopDriver)this).WaitForInput.Set ();
|
|
|
- _windowSizeChange.Set ();
|
|
|
-
|
|
|
- if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- try
|
|
|
+ if (!ConsoleDriver.RunningUnitTests)
|
|
|
{
|
|
|
- if (!_eventReadyTokenSource.IsCancellationRequested)
|
|
|
+ if (token is not Watch watch)
|
|
|
{
|
|
|
- _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- _eventReady.Reset ();
|
|
|
- }
|
|
|
|
|
|
- if (!_eventReadyTokenSource.IsCancellationRequested)
|
|
|
- {
|
|
|
- return _pollDataQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
|
|
|
+ _descriptorWatchers.Remove (watch.File);
|
|
|
}
|
|
|
-
|
|
|
- return true;
|
|
|
}
|
|
|
|
|
|
- void IMainLoopDriver.Iteration ()
|
|
|
+ private void UpdatePollMap ()
|
|
|
{
|
|
|
- // Dequeue and process the data
|
|
|
- while (_pollDataQueue.TryDequeue (out PollData inputRecords))
|
|
|
+ if (!_pollDirty)
|
|
|
{
|
|
|
- _cursesDriver.ProcessInput (inputRecords);
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- void IMainLoopDriver.TearDown ()
|
|
|
- {
|
|
|
- AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed;
|
|
|
-
|
|
|
- _inputHandlerTokenSource.Cancel ();
|
|
|
- _inputHandlerTokenSource.Dispose ();
|
|
|
- ((IMainLoopDriver)this).WaitForInput?.Dispose ();
|
|
|
|
|
|
- _windowSizeChange.Dispose();
|
|
|
+ _pollDirty = false;
|
|
|
|
|
|
- _pollDataQueue.Clear ();
|
|
|
+ _pollMap = new Pollfd [_descriptorWatchers.Count];
|
|
|
+ var i = 0;
|
|
|
|
|
|
- _eventReadyTokenSource.Cancel ();
|
|
|
- _eventReadyTokenSource.Dispose ();
|
|
|
- _eventReady.Dispose ();
|
|
|
-
|
|
|
- _mainLoop = null;
|
|
|
+ foreach (int fd in _descriptorWatchers.Keys)
|
|
|
+ {
|
|
|
+ _pollMap [i].fd = fd;
|
|
|
+ _pollMap [i].events = (short)_descriptorWatchers [fd].Condition;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
internal void WriteRaw (string ansiRequest)
|
|
@@ -421,21 +219,24 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
|
|
|
write (STDOUT_FILENO, ansiRequest, ansiRequest.Length);
|
|
|
}
|
|
|
|
|
|
+ [DllImport ("libc")]
|
|
|
+ private static extern int pipe ([In][Out] int [] pipes);
|
|
|
+
|
|
|
[DllImport ("libc")]
|
|
|
private static extern int poll ([In] [Out] Pollfd [] ufds, uint nfds, int timeout);
|
|
|
|
|
|
[DllImport ("libc")]
|
|
|
private static extern int read (int fd, nint buf, nint n);
|
|
|
|
|
|
+ [DllImport ("libc")]
|
|
|
+ private static extern int write (int fd, nint buf, nint n);
|
|
|
+
|
|
|
// File descriptor for stdout
|
|
|
private const int STDOUT_FILENO = 1;
|
|
|
|
|
|
[DllImport ("libc")]
|
|
|
private static extern int write (int fd, string buf, int n);
|
|
|
|
|
|
- [DllImport ("libc", SetLastError = true)]
|
|
|
- private static extern int ioctl (int fd, int request, ref Winsize ws);
|
|
|
-
|
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
|
private struct Pollfd
|
|
|
{
|
|
@@ -444,74 +245,10 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
|
|
|
public readonly short revents;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Window or terminal size structure. This information is stored by the kernel in order to provide a consistent
|
|
|
- /// interface, but is not used by the kernel.
|
|
|
- /// </summary>
|
|
|
- [StructLayout (LayoutKind.Sequential)]
|
|
|
- public struct Winsize
|
|
|
- {
|
|
|
- public ushort ws_row; // Number of rows
|
|
|
- public ushort ws_col; // Number of columns
|
|
|
- public ushort ws_xpixel; // Width in pixels (unused)
|
|
|
- public ushort ws_ypixel; // Height in pixels (unused)
|
|
|
- }
|
|
|
-
|
|
|
- #region Events
|
|
|
-
|
|
|
- public enum EventType
|
|
|
- {
|
|
|
- Key = 1,
|
|
|
- Mouse = 2,
|
|
|
- WindowSize = 3
|
|
|
- }
|
|
|
-
|
|
|
- public struct MouseEvent
|
|
|
- {
|
|
|
- public Point Position;
|
|
|
- public MouseFlags MouseFlags;
|
|
|
- }
|
|
|
-
|
|
|
- public struct WindowSizeEvent
|
|
|
+ private class Watch
|
|
|
{
|
|
|
- public Size Size;
|
|
|
+ public Func<MainLoop, bool> Callback;
|
|
|
+ public Condition Condition;
|
|
|
+ public int File;
|
|
|
}
|
|
|
-
|
|
|
- public struct PollData
|
|
|
- {
|
|
|
- public EventType EventType;
|
|
|
- public ConsoleKeyInfo KeyEvent;
|
|
|
- public MouseEvent MouseEvent;
|
|
|
- public WindowSizeEvent WindowSizeEvent;
|
|
|
-
|
|
|
- public readonly override string ToString ()
|
|
|
- {
|
|
|
- return (EventType switch
|
|
|
- {
|
|
|
- EventType.Key => ToString (KeyEvent),
|
|
|
- EventType.Mouse => MouseEvent.ToString (),
|
|
|
- EventType.WindowSize => WindowSizeEvent.ToString (),
|
|
|
- _ => "Unknown event type: " + EventType
|
|
|
- })!;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>Prints a ConsoleKeyInfoEx structure</summary>
|
|
|
- /// <param name="cki"></param>
|
|
|
- /// <returns></returns>
|
|
|
- public readonly string ToString (ConsoleKeyInfo cki)
|
|
|
- {
|
|
|
- var ke = new Key ((KeyCode)cki.KeyChar);
|
|
|
- var sb = new StringBuilder ();
|
|
|
- sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
|
|
|
- sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
|
|
|
- sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
|
|
|
- sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
|
|
|
- sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
|
|
|
- string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
|
|
|
-
|
|
|
- return $"[ConsoleKeyInfo({s})]";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
}
|