AnsiEscapeSequenceRequests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Terminal.Gui;
  5. namespace UICatalog.Scenarios;
  6. [ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")]
  7. [ScenarioCategory ("Tests")]
  8. public sealed class AnsiEscapeSequenceRequests : Scenario
  9. {
  10. private readonly List<DateTime> _sends = new ();
  11. private readonly object _lockAnswers = new ();
  12. private readonly Dictionary<DateTime, string> _answers = new ();
  13. private readonly object _lockErrors = new ();
  14. private readonly Dictionary<DateTime, string> _errors = new ();
  15. private GraphView _graphView;
  16. private ScatterSeries _sentSeries;
  17. private ScatterSeries _answeredSeries;
  18. private Label _lblSummary;
  19. private Label _lblErrorSummary;
  20. public override void Main ()
  21. {
  22. // Init
  23. Application.Init ();
  24. var tv = new TabView
  25. {
  26. Width = Dim.Fill (),
  27. Height = Dim.Fill ()
  28. };
  29. var single = new Tab ();
  30. single.DisplayText = "_Single";
  31. single.View = BuildSingleTab ();
  32. Tab bulk = new ();
  33. bulk.DisplayText = "_Multi";
  34. bulk.View = BuildBulkTab ();
  35. tv.AddTab (single, true);
  36. tv.AddTab (bulk, false);
  37. // Setup - Create a top-level application window and configure it.
  38. Window appWindow = new ()
  39. {
  40. Title = GetQuitKeyAndName ()
  41. };
  42. appWindow.Add (tv);
  43. // Run - Start the application.
  44. Application.Run (appWindow);
  45. bulk.View.Dispose ();
  46. single.View.Dispose ();
  47. appWindow.Dispose ();
  48. // Shutdown - Calling Application.Shutdown is required.
  49. Application.Shutdown ();
  50. }
  51. private View BuildBulkTab ()
  52. {
  53. var w = new View
  54. {
  55. Width = Dim.Fill (),
  56. Height = Dim.Fill (),
  57. CanFocus = true
  58. };
  59. var lbl = new Label
  60. {
  61. Text =
  62. "_This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.",
  63. Height = 2,
  64. Width = Dim.Fill ()
  65. };
  66. Application.AddTimeout (
  67. TimeSpan.FromMilliseconds (1000),
  68. () =>
  69. {
  70. lock (_lockAnswers)
  71. {
  72. UpdateGraph ();
  73. UpdateResponses ();
  74. }
  75. return true;
  76. });
  77. var tv = new TextView
  78. {
  79. Y = Pos.Bottom (lbl),
  80. Width = Dim.Percent (50),
  81. Height = Dim.Fill ()
  82. };
  83. var lblDar = new Label
  84. {
  85. Y = Pos.Bottom (lbl),
  86. X = Pos.Right (tv) + 1,
  87. Text = "_DAR per second: "
  88. };
  89. var cbDar = new NumericUpDown
  90. {
  91. X = Pos.Right (lblDar),
  92. Y = Pos.Bottom (lbl),
  93. Value = 0
  94. };
  95. cbDar.ValueChanging += (s, e) =>
  96. {
  97. if (e.NewValue < 0 || e.NewValue > 20)
  98. {
  99. e.Cancel = true;
  100. }
  101. };
  102. w.Add (cbDar);
  103. int lastSendTime = Environment.TickCount;
  104. var lockObj = new object ();
  105. Application.AddTimeout (
  106. TimeSpan.FromMilliseconds (50),
  107. () =>
  108. {
  109. lock (lockObj)
  110. {
  111. if (cbDar.Value > 0)
  112. {
  113. int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds
  114. int currentTime = Environment.TickCount; // Current system time in milliseconds
  115. // Check if the time elapsed since the last send is greater than the interval
  116. if (currentTime - lastSendTime >= interval)
  117. {
  118. SendDar (); // Send the request
  119. lastSendTime = currentTime; // Update the last send time
  120. }
  121. }
  122. }
  123. return true;
  124. });
  125. _graphView = new ()
  126. {
  127. Y = Pos.Bottom (cbDar),
  128. X = Pos.Right (tv),
  129. Width = Dim.Fill (),
  130. Height = Dim.Fill (2)
  131. };
  132. _lblSummary = new ()
  133. {
  134. Y = Pos.Bottom (_graphView),
  135. X = Pos.Right (tv),
  136. Width = Dim.Fill ()
  137. };
  138. _lblErrorSummary = new ()
  139. {
  140. Y = Pos.Bottom (_lblSummary),
  141. X = Pos.Right (tv),
  142. Width = Dim.Fill ()
  143. };
  144. SetupGraph ();
  145. w.Add (lbl);
  146. w.Add (lblDar);
  147. w.Add (cbDar);
  148. w.Add (tv);
  149. w.Add (_graphView);
  150. w.Add (_lblSummary);
  151. w.Add (_lblErrorSummary);
  152. return w;
  153. }
  154. private View BuildSingleTab ()
  155. {
  156. var w = new View
  157. {
  158. Width = Dim.Fill (),
  159. Height = Dim.Fill (),
  160. CanFocus = true
  161. };
  162. w.Padding.Thickness = new (1);
  163. // TODO: This hackery is why I think the EscSeqUtils class should be refactored and the CSI's made type safe.
  164. List<string> scrRequests = new ()
  165. {
  166. "CSI_SendDeviceAttributes",
  167. "CSI_ReportTerminalSizeInChars",
  168. "CSI_RequestCursorPositionReport",
  169. "CSI_SendDeviceAttributes2"
  170. };
  171. var cbRequests = new ComboBox { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper<string> (new (scrRequests)) };
  172. w.Add (cbRequests);
  173. // TODO: Use Pos.Align and Dim.Func so these hardcoded widths aren't needed.
  174. var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "_Request:" };
  175. var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 };
  176. w.Add (label, tfRequest);
  177. label = new () { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "E_xpectedResponseValue:" };
  178. var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 };
  179. w.Add (label, tfValue);
  180. label = new () { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "_Terminator:" };
  181. var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 };
  182. w.Add (label, tfTerminator);
  183. cbRequests.SelectedItemChanged += (s, e) =>
  184. {
  185. if (cbRequests.SelectedItem == -1)
  186. {
  187. return;
  188. }
  189. string selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem];
  190. AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null;
  191. switch (selAnsiEscapeSequenceRequestName)
  192. {
  193. case "CSI_SendDeviceAttributes":
  194. selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes;
  195. break;
  196. case "CSI_ReportTerminalSizeInChars":
  197. selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_ReportTerminalSizeInChars;
  198. break;
  199. case "CSI_RequestCursorPositionReport":
  200. selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_RequestCursorPositionReport;
  201. break;
  202. case "CSI_SendDeviceAttributes2":
  203. selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes2;
  204. break;
  205. }
  206. tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : "";
  207. tfValue.Text = selAnsiEscapeSequenceRequest is { }
  208. ? selAnsiEscapeSequenceRequest.ExpectedResponseValue ?? ""
  209. : "";
  210. tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : "";
  211. };
  212. // Forces raise cbRequests.SelectedItemChanged to update TextFields
  213. cbRequests.SelectedItem = 0;
  214. label = new () { Y = Pos.Bottom (tfRequest) + 2, Text = "_Response:" };
  215. var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true };
  216. w.Add (label, tvResponse);
  217. label = new () { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "_Error:" };
  218. var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true };
  219. w.Add (label, tvError);
  220. label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "E_xpectedResponseValue:" };
  221. var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true };
  222. w.Add (label, tvValue);
  223. label = new () { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "_Terminator:" };
  224. var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true };
  225. w.Add (label, tvTerminator);
  226. var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "_Send Request", IsDefault = true };
  227. var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 };
  228. w.Add (lblSuccess);
  229. btnResponse.Accepting += (s, e) =>
  230. {
  231. var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest
  232. {
  233. Request = tfRequest.Text,
  234. Terminator = tfTerminator.Text,
  235. ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
  236. };
  237. bool success = Application.Driver!.TryWriteAnsiRequest (
  238. ansiEscapeSequenceRequest
  239. );
  240. tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? "";
  241. tvError.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Error ?? "";
  242. tvValue.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.ExpectedResponseValue ?? "";
  243. tvTerminator.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Terminator ?? "";
  244. if (success)
  245. {
  246. lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"];
  247. lblSuccess.Text = "Success";
  248. }
  249. else
  250. {
  251. lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"];
  252. lblSuccess.Text = "Error";
  253. }
  254. };
  255. w.Add (btnResponse);
  256. w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "Send other requests by editing the TextFields." });
  257. return w;
  258. }
  259. private string GetSummary ()
  260. {
  261. if (_answers.Count == 0)
  262. {
  263. return "No requests sent yet";
  264. }
  265. string last = _answers.Last ().Value;
  266. int unique = _answers.Values.Distinct ().Count ();
  267. int total = _answers.Count;
  268. return $"Last:{last} U:{unique} T:{total}";
  269. }
  270. private string GetSummaryErrors ()
  271. {
  272. if (_errors.Count == 0)
  273. {
  274. return "No errors received yet";
  275. }
  276. string last = _errors.Last ().Value;
  277. int unique = _errors.Values.Distinct ().Count ();
  278. int total = _errors.Count;
  279. return $"Last:{last} U:{unique} T:{total}";
  280. }
  281. private void HandleResponse (string response)
  282. {
  283. lock (_lockAnswers)
  284. {
  285. _answers.Add (DateTime.Now, response);
  286. }
  287. }
  288. private void HandleResponseError (string response)
  289. {
  290. lock (_lockAnswers)
  291. {
  292. _errors.Add (DateTime.Now, response);
  293. }
  294. }
  295. private void SendDar ()
  296. {
  297. _sends.Add (DateTime.Now);
  298. AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes;
  299. if (Application.Driver!.TryWriteAnsiRequest (ansiRequest))
  300. {
  301. HandleResponse (ansiRequest.AnsiEscapeSequenceResponse?.Response);
  302. }
  303. else
  304. {
  305. HandleResponseError (ansiRequest.AnsiEscapeSequenceResponse?.Response);
  306. }
  307. }
  308. private void SetupGraph ()
  309. {
  310. _graphView.Series.Add (_sentSeries = new ());
  311. _graphView.Series.Add (_answeredSeries = new ());
  312. _sentSeries.Fill = new (new ('.'), new (ColorName16.BrightGreen, ColorName16.Black));
  313. _answeredSeries.Fill = new (new ('.'), new (ColorName16.BrightRed, ColorName16.Black));
  314. // Todo:
  315. // _graphView.Annotations.Add (_sentSeries new PathAnnotation {});
  316. _graphView.CellSize = new (1, 1);
  317. _graphView.MarginBottom = 2;
  318. _graphView.AxisX.Increment = 1;
  319. _graphView.AxisX.Text = "Seconds";
  320. _graphView.GraphColor = new Attribute (Color.Green, Color.Black);
  321. }
  322. private int ToSeconds (DateTime t) { return (int)(DateTime.Now - t).TotalSeconds; }
  323. private void UpdateGraph ()
  324. {
  325. _sentSeries.Points = _sends
  326. .GroupBy (ToSeconds)
  327. .Select (g => new PointF (g.Key, g.Count ()))
  328. .ToList ();
  329. _answeredSeries.Points = _answers.Keys
  330. .GroupBy (ToSeconds)
  331. .Select (g => new PointF (g.Key, g.Count ()))
  332. .ToList ();
  333. // _graphView.ScrollOffset = new PointF(,0);
  334. if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0)
  335. {
  336. _graphView.SetNeedsDisplay ();
  337. }
  338. }
  339. private void UpdateResponses ()
  340. {
  341. _lblSummary.Text = GetSummary ();
  342. _lblSummary.SetNeedsDisplay ();
  343. _lblErrorSummary.Text = GetSummaryErrors ();
  344. _lblErrorSummary.SetNeedsDisplay ();
  345. }
  346. }