InputProcessor.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #nullable enable
  2. using System.Collections.Concurrent;
  3. using Microsoft.Extensions.Logging;
  4. namespace Terminal.Gui.Drivers;
  5. /// <summary>
  6. /// Processes the queued input buffer contents - which must be of Type <typeparamref name="T"/>.
  7. /// Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
  8. /// events and data models.
  9. /// </summary>
  10. public abstract class InputProcessor<T> : IInputProcessor
  11. {
  12. /// <summary>
  13. /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
  14. /// </summary>
  15. private readonly TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
  16. internal AnsiResponseParser<T> Parser { get; } = new ();
  17. /// <summary>
  18. /// Class responsible for translating the driver specific native input class <typeparamref name="T"/> e.g.
  19. /// <see cref="ConsoleKeyInfo"/> into the Terminal.Gui <see cref="Key"/> class (used for all
  20. /// internal library representations of Keys).
  21. /// </summary>
  22. public IKeyConverter<T> KeyConverter { get; }
  23. /// <summary>
  24. /// Input buffer which will be drained from by this class.
  25. /// </summary>
  26. public ConcurrentQueue<T> InputBuffer { get; }
  27. /// <inheritdoc />
  28. public string DriverName { get; init; }
  29. /// <inheritdoc/>
  30. public IAnsiResponseParser GetParser () { return Parser; }
  31. private readonly MouseInterpreter _mouseInterpreter = new ();
  32. /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
  33. public event EventHandler<Key>? KeyDown;
  34. /// <summary>Event fired when a terminal sequence read from input is not recognized and therefore ignored.</summary>
  35. public event EventHandler<string>? AnsiSequenceSwallowed;
  36. /// <summary>
  37. /// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
  38. /// <see cref="OnKeyUp"/>.
  39. /// </summary>
  40. /// <param name="a"></param>
  41. public void OnKeyDown (Key a)
  42. {
  43. Logging.Trace ($"{nameof (InputProcessor<T>)} raised {a}");
  44. KeyDown?.Invoke (this, a);
  45. }
  46. /// <summary>Event fired when a key is released.</summary>
  47. /// <remarks>
  48. /// Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
  49. /// complete.
  50. /// </remarks>
  51. public event EventHandler<Key>? KeyUp;
  52. /// <summary>Called when a key is released. Fires the <see cref="KeyUp"/> event.</summary>
  53. /// <remarks>
  54. /// Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
  55. /// is complete.
  56. /// </remarks>
  57. /// <param name="a"></param>
  58. public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
  59. /// <summary>Event fired when a mouse event occurs.</summary>
  60. public event EventHandler<MouseEventArgs>? MouseEvent;
  61. /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
  62. /// <param name="a"></param>
  63. public void OnMouseEvent (MouseEventArgs a)
  64. {
  65. // Ensure ScreenPosition is set
  66. a.ScreenPosition = a.Position;
  67. foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
  68. {
  69. // Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
  70. // Pass on
  71. MouseEvent?.Invoke (this, e);
  72. }
  73. }
  74. /// <summary>
  75. /// Constructs base instance including wiring all relevant
  76. /// parser events and setting <see cref="InputBuffer"/> to
  77. /// the provided thread safe input collection.
  78. /// </summary>
  79. /// <param name="inputBuffer">The collection that will be populated with new input (see <see cref="IConsoleInput{T}"/>)</param>
  80. /// <param name="keyConverter">
  81. /// Key converter for translating driver specific
  82. /// <typeparamref name="T"/> class into Terminal.Gui <see cref="Key"/>.
  83. /// </param>
  84. protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyConverter)
  85. {
  86. InputBuffer = inputBuffer;
  87. Parser.HandleMouse = true;
  88. Parser.Mouse += (s, e) => OnMouseEvent (e);
  89. Parser.HandleKeyboard = true;
  90. Parser.Keyboard += (s, k) =>
  91. {
  92. OnKeyDown (k);
  93. OnKeyUp (k);
  94. };
  95. // TODO: For now handle all other escape codes with ignore
  96. Parser.UnexpectedResponseHandler = str =>
  97. {
  98. var cur = new string (str.Select (k => k.Item1).ToArray ());
  99. Logging.Logger.LogInformation ($"{nameof (InputProcessor<T>)} ignored unrecognized response '{cur}'");
  100. AnsiSequenceSwallowed?.Invoke (this, cur);
  101. return true;
  102. };
  103. KeyConverter = keyConverter;
  104. }
  105. /// <summary>
  106. /// Drains the <see cref="InputBuffer"/> buffer, processing all available keystrokes
  107. /// </summary>
  108. public void ProcessQueue ()
  109. {
  110. while (InputBuffer.TryDequeue (out T? input))
  111. {
  112. Process (input);
  113. }
  114. foreach (T input in ReleaseParserHeldKeysIfStale ())
  115. {
  116. ProcessAfterParsing (input);
  117. }
  118. }
  119. private IEnumerable<T> ReleaseParserHeldKeysIfStale ()
  120. {
  121. if (Parser.State is AnsiResponseParserState.ExpectingEscapeSequence or AnsiResponseParserState.InResponse
  122. && DateTime.Now - Parser.StateChangedAt > _escTimeout)
  123. {
  124. return Parser.Release ().Select (o => o.Item2);
  125. }
  126. return [];
  127. }
  128. /// <summary>
  129. /// Process the provided single input element <paramref name="input"/>. This method
  130. /// is called sequentially for each value read from <see cref="InputBuffer"/>.
  131. /// </summary>
  132. /// <param name="input"></param>
  133. protected abstract void Process (T input);
  134. /// <summary>
  135. /// Process the provided single input element - short-circuiting the <see cref="Parser"/>
  136. /// stage of the processing.
  137. /// </summary>
  138. /// <param name="input"></param>
  139. protected abstract void ProcessAfterParsing (T input);
  140. internal char _highSurrogate = '\0';
  141. /// <inheritdoc />
  142. public bool IsValidInput (Key key, out Key result)
  143. {
  144. result = key;
  145. if (char.IsHighSurrogate ((char)key))
  146. {
  147. _highSurrogate = (char)key;
  148. return false;
  149. }
  150. if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key))
  151. {
  152. result = (KeyCode)new Rune (_highSurrogate, (char)key).Value;
  153. if (key.IsAlt)
  154. {
  155. result = result.WithAlt;
  156. }
  157. if (key.IsCtrl)
  158. {
  159. result = result.WithCtrl;
  160. }
  161. if (key.IsShift)
  162. {
  163. result = result.WithShift;
  164. }
  165. _highSurrogate = '\0';
  166. return true;
  167. }
  168. if (char.IsSurrogate ((char)key))
  169. {
  170. return false;
  171. }
  172. if (_highSurrogate > 0)
  173. {
  174. _highSurrogate = '\0';
  175. }
  176. if (key.KeyCode == 0)
  177. {
  178. return false;
  179. }
  180. return true;
  181. }
  182. }