Files
Terminal.Gui/Terminal.Gui/MainLoop.cs
Tig 0df485a890 Fixes #666. Refactor ConsoleDrivers to simplify and remove duplicated code (#2612)
* Added ClipRegion; cleaned up driver code

* clip region unit tests

* api docs

* Moved color stuff from ConsoleDriver to Color.cs

* Removes unused ConsoleDriver APIs

* Code cleanup and Removes unused ConsoleDriver APIs

* Code cleanup and Removes unused ConsoleDriver APIs

* Work around https://github.com/gui-cs/Terminal.Gui/issues/2610

* adjusted unit tests

* initial commit

* Made Rows, Cols, Top, Left virtual

* Made Clipboard non-virtual

* Made EnableConsoleScrolling  non-virtual

* Made Contents non-virtual

* Pulled Row/Col up

* Made MoveTo virtual; fixed stupid FakeDriver cursor issue

* Made CurrentAttribute non-virtual

* Made SetAttribute  non-virtual

* Moved clipboard code out

* Code cleanup

* Removes dependecy on NStack from ConsoleDrivers - WIP

* Fixed unit tests

* Fixed unit tests

* Added list of unit tests needed

* Did some perf testing; tweaked code and charmap to address

* Brough in code from PR #2264 (but commented)

* Tons of code cleanup

* Fighting with ScrollView

* Fixing bugs

* Fixed TabView tests

* Fixed View.Visible test that was not really working

* Fixed unit tests

* Cleaned up clipboard APIs in attempt to track down unit test failure

* Add Cut_Preserves_Selection test

* Removed invalid code

* Removed invalid test code; unit tests now pass

* EscSeq* - Adjusted naming, added more sequences, made code more consistent, simplified, etc...

* Added CSI_SetGraphicsRendition

* NetDriver code cleanup

* code cleanup

* Cleaned up color handling in NetDriver

* refixed tabview unit test

* WindowsDriver color code cleanup

* WindowsDriver color code cleanup

* CursesDriver color code cleanup

* CursesDriver - Adding _BOLD has no effect. Further up the stack we cast the return of ColorToCursesColor from int to short and the _BOLD values don't fit in a short.

* CursesDriver color code - make code more accurate

* CursesDriver color code - make code more accurate

* Simplified ConsoleDriver.GetColors API

* Simplified ConsoleDriver.GetColors API further

* Improved encapslation of Attribute; prep for TrueColor & other attributes like blink

* Fixes #2249. CharacterMap isn't refreshing well non-BMP code points on scroll.

* Use GetRange to take some of the runes before convert to string.

* Attempting to fix unit tests not being cleaned up

* Fixes #2658 - ConsoleDriver.IsRuneSupported

* Fixes #2658 - ConsoleDriver.IsRuneSupported (for WindowsDriver)

* Check all the range values and not only the max value.

* Reducing code.

* Fixes #2674 - Unit test process doesn't exit

* Changed Cell to support IsDirty and list of Runes

* add support for rendering TrueColor output on Windows merging veeman & tznind code

* add colorconverter changes

* fixed merged v2_develop

* Fixing merge bugs

* Fixed merge bugs

* Fixed merge bugs - all unit tests pass

* Debugging netdriver

* More netdriver diag

* API docs for escutils

* Update unicode scenario to stress more stuff

* Contents: Now a 2D array of Cells; WIP

* AddRune and ClearContents no longer virtual/abstract

* WindowsDriver renders correctly again

* Progress on Curses

* Progress on Curses

* broke windowsdriver

* Cleaned up FakeMainLoop

* Cleaned up some build warnings

* Removed _init from AutoInitShutdown as it's not needed anymore

* Removed unused var

* Removed unused var

* Fixed nullabiltiy warning in LineCanvas

* Fixed charmap crash

* Fixes #2758 in v2

* Port testonfail fix to v2

* Remove EnableConsoleScrolling

* Backport #2764 from develop (clear last line)

* Remove uneeded usings

* Progress on unicode

* Merged in changes from PR #2786, Fixes #2784

* revamp charmap rendering

* Charmap option to show glyph widths

* Fixed issue with wide glpyhs being overwritten

* Fixed charmap startcodepoint change issue

* Added abiltiy to see ncurses verison/lib

* Fought with CursesDriver; giving up for now. See notes.

* Leverage Wcwidth nuget library instaed of our own tables

* enhanced charmap Details dialog

* Final attempt at fixing curses

---------

Co-authored-by: BDisp <bd.bdisp@gmail.com>
Co-authored-by: adstep <stephensonadamj@gmail.com>
2023-08-09 14:28:36 -06:00

332 lines
9.3 KiB
C#

//
// MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui
//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Terminal.Gui {
/// <summary>
/// Public interface to create a platform specific <see cref="MainLoop"/> driver.
/// </summary>
public interface IMainLoopDriver {
/// <summary>
/// Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.
/// </summary>
/// <param name="mainLoop">Main loop.</param>
void Setup (MainLoop mainLoop);
/// <summary>
/// Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.
/// </summary>
void Wakeup ();
/// <summary>
/// Must report whether there are any events pending, or even block waiting for events.
/// </summary>
/// <returns><c>true</c>, if there were pending events, <c>false</c> otherwise.</returns>
/// <param name="wait">If set to <c>true</c> wait until an event is available, otherwise return immediately.</param>
bool EventsPending (bool wait);
/// <summary>
/// The iteration function.
/// </summary>
void Iteration ();
}
/// <summary>
/// Simple main loop implementation that can be used to monitor
/// file descriptor, run timers and idle handlers.
/// </summary>
/// <remarks>
/// Monitoring of file descriptors is only available on Unix, there
/// does not seem to be a way of supporting this on Windows.
/// </remarks>
public class MainLoop {
internal SortedList<long, Timeout> timeouts = new SortedList<long, Timeout> ();
object _timeoutsLockToken = new object ();
/// <summary>
/// The idle handlers and lock that must be held while manipulating them
/// </summary>
object _idleHandlersLock = new object ();
internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
/// <summary>
/// Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks./>.
/// A shorter limit time can be added at the end, but it will be called before an
/// earlier addition that has a longer limit time.
/// </summary>
public SortedList<long, Timeout> Timeouts => timeouts;
/// <summary>
/// Gets a copy of the list of all idle handlers.
/// </summary>
public ReadOnlyCollection<Func<bool>> IdleHandlers {
get {
lock (_idleHandlersLock) {
return new List<Func<bool>> (idleHandlers).AsReadOnly ();
}
}
}
/// <summary>
/// The current <see cref="IMainLoopDriver"/> in use.
/// </summary>
/// <value>The main loop driver.</value>
public IMainLoopDriver MainLoopDriver { get; }
/// <summary>
/// Invoked when a new timeout is added. To be used in the case
/// when <see cref="Application.ExitRunLoopAfterFirstIteration"/> is <see langword="true"/>.
/// </summary>
public event EventHandler<TimeoutEventArgs> TimeoutAdded;
/// <summary>
/// Creates a new Mainloop.
/// </summary>
/// <param name="driver">Should match the <see cref="ConsoleDriver"/>
/// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
public MainLoop (IMainLoopDriver driver)
{
MainLoopDriver = driver;
driver.Setup (this);
}
/// <summary>
/// Runs <c>action</c> on the thread that is processing events
/// </summary>
/// <param name="action">the action to be invoked on the main processing thread.</param>
public void Invoke (Action action)
{
AddIdle (() => {
action ();
return false;
});
}
/// <summary>
/// Adds specified idle handler function to <see cref="MainLoop"/> processing.
/// The handler function will be called once per iteration of the main loop after other events have been handled.
/// </summary>
/// <remarks>
/// <para>
/// Remove an idle hander by calling <see cref="RemoveIdle(Func{bool})"/> with the token this method returns.
/// </para>
/// <para>
/// If the <c>idleHandler</c> returns <c>false</c> it will be removed and not called subsequently.
/// </para>
/// </remarks>
/// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="RemoveIdle(Func{bool})"/> .</param>
public Func<bool> AddIdle (Func<bool> idleHandler)
{
lock (_idleHandlersLock) {
idleHandlers.Add (idleHandler);
}
MainLoopDriver.Wakeup ();
return idleHandler;
}
/// <summary>
/// Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.
/// </summary>
/// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
/// Returns <c>true</c>if the idle handler is successfully removed; otherwise, <c>false</c>.
/// This method also returns <c>false</c> if the idle handler is not found.
public bool RemoveIdle (Func<bool> token)
{
lock (_idleHandlersLock) {
return idleHandlers.Remove (token);
}
}
void AddTimeout (TimeSpan time, Timeout timeout)
{
lock (_timeoutsLockToken) {
var k = (DateTime.UtcNow + time).Ticks;
timeouts.Add (NudgeToUniqueKey (k), timeout);
TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
}
}
/// <summary>
/// Adds a timeout to the <see cref="MainLoop"/>.
/// </summary>
/// <remarks>
/// When time specified passes, the callback will be invoked.
/// If the callback returns true, the timeout will be reset, repeating
/// the invocation. If it returns false, the timeout will stop and be removed.
///
/// The returned value is a token that can be used to stop the timeout
/// by calling <see cref="RemoveTimeout(object)"/>.
/// </remarks>
public object AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
{
if (callback == null) {
throw new ArgumentNullException (nameof (callback));
}
var timeout = new Timeout () {
Span = time,
Callback = callback
};
AddTimeout (time, timeout);
return timeout;
}
/// <summary>
/// Removes a previously scheduled timeout
/// </summary>
/// <remarks>
/// The token parameter is the value returned by AddTimeout.
/// </remarks>
/// Returns <c>true</c>if the timeout is successfully removed; otherwise, <c>false</c>.
/// This method also returns <c>false</c> if the timeout is not found.
public bool RemoveTimeout (object token)
{
lock (_timeoutsLockToken) {
var idx = timeouts.IndexOfValue (token as Timeout);
if (idx == -1) {
return false;
}
timeouts.RemoveAt (idx);
}
return true;
}
void RunTimers ()
{
long now = DateTime.UtcNow.Ticks;
SortedList<long, Timeout> copy;
// lock prevents new timeouts being added
// after we have taken the copy but before
// we have allocated a new list (which would
// result in lost timeouts or errors during enumeration)
lock (_timeoutsLockToken) {
copy = timeouts;
timeouts = new SortedList<long, Timeout> ();
}
foreach (var t in copy) {
var k = t.Key;
var timeout = t.Value;
if (k < now) {
if (timeout.Callback (this)) {
AddTimeout (timeout.Span, timeout);
}
} else {
lock (_timeoutsLockToken) {
timeouts.Add (NudgeToUniqueKey (k), timeout);
}
}
}
}
/// <summary>
/// Finds the closest number to <paramref name="k"/> that is not
/// present in <see cref="timeouts"/> (incrementally).
/// </summary>
/// <param name="k"></param>
/// <returns></returns>
private long NudgeToUniqueKey (long k)
{
lock (_timeoutsLockToken) {
while (timeouts.ContainsKey (k)) {
k++;
}
}
return k;
}
void RunIdle ()
{
List<Func<bool>> iterate;
lock (_idleHandlersLock) {
iterate = idleHandlers;
idleHandlers = new List<Func<bool>> ();
}
foreach (var idle in iterate) {
if (idle ()) {
lock (_idleHandlersLock) {
idleHandlers.Add (idle);
}
}
}
}
bool _running;
// BUGBUG: Stop is only called from MainLoopUnitTests.cs. As a result, the mainloop
// will never exit during other unit tests or normal execution.
/// <summary>
/// Stops the mainloop.
/// </summary>
public void Stop ()
{
_running = false;
MainLoopDriver.Wakeup ();
}
/// <summary>
/// Determines whether there are pending events to be processed.
/// </summary>
/// <remarks>
/// You can use this method if you want to probe if events are pending.
/// Typically used if you need to flush the input queue while still
/// running some of your own code in your main thread.
/// </remarks>
public bool EventsPending (bool wait = false)
{
return MainLoopDriver.EventsPending (wait);
}
/// <summary>
/// Runs one iteration of timers and file watches
/// </summary>
/// <remarks>
/// Use this to process all pending events (timers, idle handlers and file watches).
///
/// <code>
/// while (main.EvenstPending ()) RunIteration ();
/// </code>
/// </remarks>
public void RunIteration ()
{
if (timeouts.Count > 0) {
RunTimers ();
}
MainLoopDriver.Iteration ();
bool runIdle = false;
lock (_idleHandlersLock) {
runIdle = idleHandlers.Count > 0;
}
if (runIdle) {
RunIdle ();
}
}
/// <summary>
/// Runs the <see cref="MainLoop"/>.
/// </summary>
public void Run ()
{
bool prev = _running;
_running = true;
while (_running) {
EventsPending (true);
RunIteration ();
}
_running = prev;
}
}
}