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