From b95edf93971d91ab2c471f6e4783c0ea15d81bd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 15:19:10 +0000 Subject: [PATCH] Subscribe to IsModalChanged instead of checking IsModal in SessionBegun - Changed example mode to subscribe to TopRunnable.IsModalChanged event - When runnable becomes modal (e.Value == true), demo keys are sent - If runnable is already modal when SessionBegun fires, keys are sent immediately - Demo keys sent asynchronously via Task.Run to avoid blocking UI thread - Uses async/await with Task.Delay instead of Thread.Sleep for better responsiveness This addresses @tig's feedback to use IsModalChanged event instead of just checking IsModal property. Note: Examples still timing out - key injection mechanism needs further investigation. Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Terminal.Gui/App/ApplicationImpl.Lifecycle.cs | 119 +++++++++++------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs index 4a678f778..622826012 100644 --- a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs +++ b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs @@ -397,24 +397,44 @@ internal partial class ApplicationImpl /// /// Sets up example mode functionality - collecting metadata and sending demo keys - /// when the first TopRunnable is modal. + /// when the first TopRunnable becomes modal. /// private void SetupExampleMode () { - // Subscribe to SessionBegun to wait for the first modal runnable + // Subscribe to SessionBegun to monitor when runnables start SessionBegun += OnSessionBegunForExample; } private void OnSessionBegunForExample (object? sender, SessionTokenEventArgs e) { - // Only send demo keys once, when the first modal runnable appears + // Only send demo keys once if (_exampleModeDemoKeysSent) { return; } - // Check if the TopRunnable is modal - if (TopRunnable?.IsModal != true) + // Subscribe to IsModalChanged event on the TopRunnable + if (TopRunnable is { }) + { + TopRunnable.IsModalChanged += OnIsModalChangedForExample; + + // Check if already modal - if so, send keys immediately + if (TopRunnable.IsModal) + { + _exampleModeDemoKeysSent = true; + TopRunnable.IsModalChanged -= OnIsModalChangedForExample; + SendDemoKeys (); + } + } + + // Unsubscribe from SessionBegun - we only need to set up the modal listener once + SessionBegun -= OnSessionBegunForExample; + } + + private void OnIsModalChangedForExample (object? sender, EventArgs e) + { + // Only send demo keys once, when a runnable becomes modal (not when it stops being modal) + if (_exampleModeDemoKeysSent || !e.Value) { return; } @@ -423,7 +443,10 @@ internal partial class ApplicationImpl _exampleModeDemoKeysSent = true; // Unsubscribe - we only need to do this once - SessionBegun -= OnSessionBegunForExample; + if (TopRunnable is { }) + { + TopRunnable.IsModalChanged -= OnIsModalChangedForExample; + } // Send demo keys from assembly attributes SendDemoKeys (); @@ -452,61 +475,65 @@ internal partial class ApplicationImpl // Sort by Order and collect all keystrokes var sortedSequences = demoKeyAttributes.OrderBy (a => a.Order); - // Default delay between keys is 100ms - int currentDelay = 100; - - foreach (var attr in sortedSequences) + // Send keys asynchronously to avoid blocking the UI thread + Task.Run (async () => { - // Handle KeyStrokes array - if (attr.KeyStrokes is { Length: > 0 }) + // Default delay between keys is 100ms + int currentDelay = 100; + + foreach (var attr in sortedSequences) { - foreach (string keyStr in attr.KeyStrokes) + // Handle KeyStrokes array + if (attr.KeyStrokes is { Length: > 0 }) { - // Check for SetDelay command - if (keyStr.StartsWith ("SetDelay:", StringComparison.OrdinalIgnoreCase)) + foreach (string keyStr in attr.KeyStrokes) { - string delayValue = keyStr.Substring ("SetDelay:".Length); - - if (int.TryParse (delayValue, out int newDelay)) + // Check for SetDelay command + if (keyStr.StartsWith ("SetDelay:", StringComparison.OrdinalIgnoreCase)) { - currentDelay = newDelay; + string delayValue = keyStr.Substring ("SetDelay:".Length); + + if (int.TryParse (delayValue, out int newDelay)) + { + currentDelay = newDelay; + } + + continue; } - continue; + // Regular key + if (Input.Key.TryParse (keyStr, out Input.Key? key) && key is { }) + { + // Apply delay before sending key + if (currentDelay > 0) + { + await Task.Delay (currentDelay); + } + + Keyboard?.RaiseKeyDownEvent (key); + } } + } - // Regular key - if (Input.Key.TryParse (keyStr, out Input.Key? key) && key is { }) + // Handle RepeatKey + if (!string.IsNullOrEmpty (attr.RepeatKey)) + { + if (Input.Key.TryParse (attr.RepeatKey, out Input.Key? key) && key is { }) { - // Apply delay before sending key - if (currentDelay > 0) + for (var i = 0; i < attr.RepeatCount; i++) { - System.Threading.Thread.Sleep (currentDelay); - } + // Apply delay before sending key + if (currentDelay > 0) + { + await Task.Delay (currentDelay); + } - Keyboard?.RaiseKeyDownEvent (key); + Keyboard?.RaiseKeyDownEvent (key); + } } } } - - // Handle RepeatKey - if (!string.IsNullOrEmpty (attr.RepeatKey)) - { - if (Input.Key.TryParse (attr.RepeatKey, out Input.Key? key) && key is { }) - { - for (var i = 0; i < attr.RepeatCount; i++) - { - // Apply delay before sending key - if (currentDelay > 0) - { - System.Threading.Thread.Sleep (currentDelay); - } - - Keyboard?.RaiseKeyDownEvent (key); - } - } - } - } + }); } #endregion Example Mode