From f4a556cb52b22f4fd5a991cb3190568e63c74625 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 01:15:41 +0000
Subject: [PATCH 1/2] Refactor ConfigurationProperty properties to use static
backing fields and raise events
Co-authored-by: tig <585482+tig@users.noreply.github.com>
---
Terminal.Gui/App/Application.Driver.cs | 36 ++++++-----
Terminal.Gui/App/Application.Mouse.cs | 14 ++++-
Terminal.Gui/App/Application.Navigation.cs | 59 ++++++++++++++---
Terminal.Gui/App/Application.Run.cs | 28 +++++++--
Terminal.Gui/App/ApplicationImpl.Lifecycle.cs | 34 ++++++++++
Terminal.Gui/App/ApplicationImpl.cs | 18 +++++-
Terminal.Gui/App/Keyboard/KeyboardImpl.cs | 63 ++++++++++++++++++-
Terminal.Gui/App/Mouse/MouseImpl.cs | 26 +++++++-
8 files changed, 240 insertions(+), 38 deletions(-)
diff --git a/Terminal.Gui/App/Application.Driver.cs b/Terminal.Gui/App/Application.Driver.cs
index 635ff854b..427ba4de5 100644
--- a/Terminal.Gui/App/Application.Driver.cs
+++ b/Terminal.Gui/App/Application.Driver.cs
@@ -13,38 +13,44 @@ public static partial class Application // Driver abstractions
internal set => ApplicationImpl.Instance.Driver = value;
}
+ private static bool _force16Colors = false; // Resources/config.json overrides
+
///
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]
public static bool Force16Colors
{
- get => ApplicationImpl.Instance.Force16Colors;
- set => ApplicationImpl.Instance.Force16Colors = value;
+ get => _force16Colors;
+ set
+ {
+ bool oldValue = _force16Colors;
+ _force16Colors = value;
+ Force16ColorsChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _force16Colors));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? Force16ColorsChanged;
+
+ private static string _forceDriver = string.Empty; // Resources/config.json overrides
+
///
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]
public static string ForceDriver
{
- get => ApplicationImpl.Instance.ForceDriver;
+ get => _forceDriver;
set
{
- if (!string.IsNullOrEmpty (ApplicationImpl.Instance.ForceDriver) && value != Driver?.GetName ())
- {
- // ForceDriver cannot be changed if it has a valid value
- return;
- }
-
- if (ApplicationImpl.Instance.Initialized && value != Driver?.GetName ())
- {
- throw new InvalidOperationException ($"The {nameof (ForceDriver)} can only be set before initialized.");
- }
-
- ApplicationImpl.Instance.ForceDriver = value;
+ string oldValue = _forceDriver;
+ _forceDriver = value;
+ ForceDriverChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _forceDriver));
}
}
+ /// Raised when changes.
+ public static event EventHandler>? ForceDriverChanged;
+
///
[Obsolete ("The legacy static Application object is going away.")]
public static List Sixel => ApplicationImpl.Instance.Sixel;
diff --git a/Terminal.Gui/App/Application.Mouse.cs b/Terminal.Gui/App/Application.Mouse.cs
index 5e6ec118f..2ea9bb650 100644
--- a/Terminal.Gui/App/Application.Mouse.cs
+++ b/Terminal.Gui/App/Application.Mouse.cs
@@ -4,15 +4,25 @@ namespace Terminal.Gui.App;
public static partial class Application // Mouse handling
{
+ private static bool _isMouseDisabled = false; // Resources/config.json overrides
+
/// Disable or enable the mouse. The mouse is enabled by default.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]
public static bool IsMouseDisabled
{
- get => Mouse.IsMouseDisabled;
- set => Mouse.IsMouseDisabled = value;
+ get => _isMouseDisabled;
+ set
+ {
+ bool oldValue = _isMouseDisabled;
+ _isMouseDisabled = value;
+ IsMouseDisabledChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _isMouseDisabled));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? IsMouseDisabledChanged;
+
///
/// Gets the instance that manages mouse event handling and state.
///
diff --git a/Terminal.Gui/App/Application.Navigation.cs b/Terminal.Gui/App/Application.Navigation.cs
index b1053ae42..673a76420 100644
--- a/Terminal.Gui/App/Application.Navigation.cs
+++ b/Terminal.Gui/App/Application.Navigation.cs
@@ -13,22 +13,43 @@ public static partial class Application // Navigation stuff
internal set => ApplicationImpl.Instance.Navigation = value;
}
+ private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
+
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
- [Obsolete ("The legacy static Application object is going away.")]public static Key NextTabGroupKey
+ [Obsolete ("The legacy static Application object is going away.")]
+ public static Key NextTabGroupKey
{
- get => ApplicationImpl.Instance.Keyboard.NextTabGroupKey;
- set => ApplicationImpl.Instance.Keyboard.NextTabGroupKey = value;
+ get => _nextTabGroupKey;
+ set
+ {
+ Key oldValue = _nextTabGroupKey;
+ _nextTabGroupKey = value;
+ NextTabGroupKeyChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _nextTabGroupKey));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? NextTabGroupKeyChanged;
+
+ private static Key _nextTabKey = Key.Tab; // Resources/config.json overrides
+
/// Alternative key to navigate forwards through views. Tab is the primary key.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key NextTabKey
{
- get => ApplicationImpl.Instance.Keyboard.NextTabKey;
- set => ApplicationImpl.Instance.Keyboard.NextTabKey = value;
+ get => _nextTabKey;
+ set
+ {
+ Key oldValue = _nextTabKey;
+ _nextTabKey = value;
+ NextTabKeyChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _nextTabKey));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? NextTabKeyChanged;
+
///
/// Raised when the user releases a key.
///
@@ -48,19 +69,39 @@ public static partial class Application // Navigation stuff
remove => ApplicationImpl.Instance.Keyboard.KeyUp -= value;
}
+ private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
+
/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key PrevTabGroupKey
{
- get => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey;
- set => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey = value;
+ get => _prevTabGroupKey;
+ set
+ {
+ Key oldValue = _prevTabGroupKey;
+ _prevTabGroupKey = value;
+ PrevTabGroupKeyChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _prevTabGroupKey));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? PrevTabGroupKeyChanged;
+
+ private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
+
/// Alternative key to navigate backwards through views. Shift+Tab is the primary key.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key PrevTabKey
{
- get => ApplicationImpl.Instance.Keyboard.PrevTabKey;
- set => ApplicationImpl.Instance.Keyboard.PrevTabKey = value;
+ get => _prevTabKey;
+ set
+ {
+ Key oldValue = _prevTabKey;
+ _prevTabKey = value;
+ PrevTabKeyChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _prevTabKey));
+ }
}
+
+ /// Raised when changes.
+ public static event EventHandler>? PrevTabKeyChanged;
}
diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs
index 9e6b2e064..9a20d3c96 100644
--- a/Terminal.Gui/App/Application.Run.cs
+++ b/Terminal.Gui/App/Application.Run.cs
@@ -4,22 +4,42 @@ namespace Terminal.Gui.App;
public static partial class Application // Run (Begin -> Run -> Layout/Draw -> End -> Stop)
{
+ private static Key _quitKey = Key.Esc; // Resources/config.json overrides
+
/// Gets or sets the key to quit the application.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key QuitKey
{
- get => ApplicationImpl.Instance.Keyboard.QuitKey;
- set => ApplicationImpl.Instance.Keyboard.QuitKey = value;
+ get => _quitKey;
+ set
+ {
+ Key oldValue = _quitKey;
+ _quitKey = value;
+ QuitKeyChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _quitKey));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? QuitKeyChanged;
+
+ private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
+
/// Gets or sets the key to activate arranging views using the keyboard.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key ArrangeKey
{
- get => ApplicationImpl.Instance.Keyboard.ArrangeKey;
- set => ApplicationImpl.Instance.Keyboard.ArrangeKey = value;
+ get => _arrangeKey;
+ set
+ {
+ Key oldValue = _arrangeKey;
+ _arrangeKey = value;
+ ArrangeKeyChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _arrangeKey));
+ }
}
+ /// Raised when changes.
+ public static event EventHandler>? ArrangeKeyChanged;
+
///
[Obsolete ("The legacy static Application object is going away.")]
public static SessionToken Begin (Toplevel toplevel) => ApplicationImpl.Instance.Begin (toplevel);
diff --git a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
index faf678bc6..c9ac7d3a6 100644
--- a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
+++ b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
@@ -254,6 +254,17 @@ public partial class ApplicationImpl
ClearScreenNextIteration = false;
// === 6. Reset input systems ===
+ // Dispose keyboard and mouse to unsubscribe from events
+ if (_keyboard is IDisposable keyboardDisposable)
+ {
+ keyboardDisposable.Dispose ();
+ }
+
+ if (_mouse is IDisposable mouseDisposable)
+ {
+ mouseDisposable.Dispose ();
+ }
+
// Mouse and Keyboard will be lazy-initialized on next access
_mouse = null;
_keyboard = null;
@@ -286,10 +297,33 @@ public partial class ApplicationImpl
// gui.cs does no longer process any callbacks. See #1084 for more details:
// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
SynchronizationContext.SetSynchronizationContext (null);
+
+ // === 12. Unsubscribe from Application static property change events ===
+ UnsubscribeApplicationEvents ();
}
///
/// Raises the event.
///
internal void RaiseInitializedChanged (object sender, EventArgs e) { InitializedChanged?.Invoke (sender, e); }
+
+ // Event handlers for Application static property changes
+ private void OnForce16ColorsChanged (object? sender, ValueChangedEventArgs e)
+ {
+ Force16Colors = e.NewValue;
+ }
+
+ private void OnForceDriverChanged (object? sender, ValueChangedEventArgs e)
+ {
+ ForceDriver = e.NewValue;
+ }
+
+ ///
+ /// Unsubscribes from Application static property change events.
+ ///
+ private void UnsubscribeApplicationEvents ()
+ {
+ Application.Force16ColorsChanged -= OnForce16ColorsChanged;
+ Application.ForceDriverChanged -= OnForceDriverChanged;
+ }
}
diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs
index 0cdcccea3..7c6b237df 100644
--- a/Terminal.Gui/App/ApplicationImpl.cs
+++ b/Terminal.Gui/App/ApplicationImpl.cs
@@ -9,15 +9,27 @@ namespace Terminal.Gui.App;
public partial class ApplicationImpl : IApplication
{
///
- /// INTERNAL: Creates a new instance of the Application backend.
+ /// INTERNAL: Creates a new instance of the Application backend and subscribes to Application configuration property events.
///
- internal ApplicationImpl () { }
+ internal ApplicationImpl ()
+ {
+ // Initialize from Application static properties (ConfigurationManager may have set these before we were created)
+ Force16Colors = Application.Force16Colors;
+ ForceDriver = Application.ForceDriver;
+
+ // Subscribe to Application static property change events
+ Application.Force16ColorsChanged += OnForce16ColorsChanged;
+ Application.ForceDriverChanged += OnForceDriverChanged;
+ }
///
/// INTERNAL: Creates a new instance of the Application backend.
///
///
- internal ApplicationImpl (IComponentFactory componentFactory) { _componentFactory = componentFactory; }
+ internal ApplicationImpl (IComponentFactory componentFactory) : this ()
+ {
+ _componentFactory = componentFactory;
+ }
#region Singleton
diff --git a/Terminal.Gui/App/Keyboard/KeyboardImpl.cs b/Terminal.Gui/App/Keyboard/KeyboardImpl.cs
index 75c37d4b8..e7f621dd8 100644
--- a/Terminal.Gui/App/Keyboard/KeyboardImpl.cs
+++ b/Terminal.Gui/App/Keyboard/KeyboardImpl.cs
@@ -10,7 +10,7 @@ namespace Terminal.Gui.App;
/// See for usage details.
///
///
-internal class KeyboardImpl : IKeyboard
+internal class KeyboardImpl : IKeyboard, IDisposable
{
private Key _quitKey = Key.Esc; // Resources/config.json overrides
private Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
@@ -103,10 +103,26 @@ internal class KeyboardImpl : IKeyboard
public event EventHandler? KeyUp;
///
- /// Initializes keyboard bindings.
+ /// Initializes keyboard bindings and subscribes to Application configuration property events.
///
public KeyboardImpl ()
{
+ // Initialize from Application static properties (ConfigurationManager may have set these before we were created)
+ _quitKey = Application.QuitKey;
+ _arrangeKey = Application.ArrangeKey;
+ _nextTabGroupKey = Application.NextTabGroupKey;
+ _nextTabKey = Application.NextTabKey;
+ _prevTabGroupKey = Application.PrevTabGroupKey;
+ _prevTabKey = Application.PrevTabKey;
+
+ // Subscribe to Application static property change events
+ Application.QuitKeyChanged += OnQuitKeyChanged;
+ Application.ArrangeKeyChanged += OnArrangeKeyChanged;
+ Application.NextTabGroupKeyChanged += OnNextTabGroupKeyChanged;
+ Application.NextTabKeyChanged += OnNextTabKeyChanged;
+ Application.PrevTabGroupKeyChanged += OnPrevTabGroupKeyChanged;
+ Application.PrevTabKeyChanged += OnPrevTabKeyChanged;
+
AddKeyBindings ();
}
@@ -378,4 +394,47 @@ internal class KeyboardImpl : IKeyboard
KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
}
}
+
+ // Event handlers for Application static property changes
+ private void OnQuitKeyChanged (object? sender, ValueChangedEventArgs e)
+ {
+ QuitKey = e.NewValue;
+ }
+
+ private void OnArrangeKeyChanged (object? sender, ValueChangedEventArgs e)
+ {
+ ArrangeKey = e.NewValue;
+ }
+
+ private void OnNextTabGroupKeyChanged (object? sender, ValueChangedEventArgs e)
+ {
+ NextTabGroupKey = e.NewValue;
+ }
+
+ private void OnNextTabKeyChanged (object? sender, ValueChangedEventArgs e)
+ {
+ NextTabKey = e.NewValue;
+ }
+
+ private void OnPrevTabGroupKeyChanged (object? sender, ValueChangedEventArgs e)
+ {
+ PrevTabGroupKey = e.NewValue;
+ }
+
+ private void OnPrevTabKeyChanged (object? sender, ValueChangedEventArgs e)
+ {
+ PrevTabKey = e.NewValue;
+ }
+
+ ///
+ public void Dispose ()
+ {
+ // Unsubscribe from Application static property change events
+ Application.QuitKeyChanged -= OnQuitKeyChanged;
+ Application.ArrangeKeyChanged -= OnArrangeKeyChanged;
+ Application.NextTabGroupKeyChanged -= OnNextTabGroupKeyChanged;
+ Application.NextTabKeyChanged -= OnNextTabKeyChanged;
+ Application.PrevTabGroupKeyChanged -= OnPrevTabGroupKeyChanged;
+ Application.PrevTabKeyChanged -= OnPrevTabKeyChanged;
+ }
}
diff --git a/Terminal.Gui/App/Mouse/MouseImpl.cs b/Terminal.Gui/App/Mouse/MouseImpl.cs
index 12aa4ada9..a66e08c47 100644
--- a/Terminal.Gui/App/Mouse/MouseImpl.cs
+++ b/Terminal.Gui/App/Mouse/MouseImpl.cs
@@ -9,12 +9,19 @@ namespace Terminal.Gui.App;
/// enabling better testability and parallel test execution.
///
///
-internal class MouseImpl : IMouse
+internal class MouseImpl : IMouse, IDisposable
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class and subscribes to Application configuration property events.
///
- public MouseImpl () { }
+ public MouseImpl ()
+ {
+ // Initialize from Application static property (ConfigurationManager may have set this before we were created)
+ IsMouseDisabled = Application.IsMouseDisabled;
+
+ // Subscribe to Application static property change events
+ Application.IsMouseDisabledChanged += OnIsMouseDisabledChanged;
+ }
///
public IApplication? App { get; set; }
@@ -391,4 +398,17 @@ internal class MouseImpl : IMouse
return false;
}
+
+ // Event handler for Application static property changes
+ private void OnIsMouseDisabledChanged (object? sender, ValueChangedEventArgs e)
+ {
+ IsMouseDisabled = e.NewValue;
+ }
+
+ ///
+ public void Dispose ()
+ {
+ // Unsubscribe from Application static property change events
+ Application.IsMouseDisabledChanged -= OnIsMouseDisabledChanged;
+ }
}
From 34a5dd7600999110224f1183e199d2ca3b406a10 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 01:19:12 +0000
Subject: [PATCH 2/2] Reset static Application properties in ResetStateStatic
Co-authored-by: tig <585482+tig@users.noreply.github.com>
---
Terminal.Gui/App/ApplicationImpl.cs | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs
index 7c6b237df..f160c50cc 100644
--- a/Terminal.Gui/App/ApplicationImpl.cs
+++ b/Terminal.Gui/App/ApplicationImpl.cs
@@ -120,6 +120,18 @@ public partial class ApplicationImpl : IApplication
// If an instance exists, reset it
_instance?.ResetState (ignoreDisposed);
+ // Reset Application static properties to their defaults
+ // This ensures tests start with clean state
+ Application.ForceDriver = string.Empty;
+ Application.Force16Colors = false;
+ Application.IsMouseDisabled = false;
+ Application.QuitKey = Key.Esc;
+ Application.ArrangeKey = Key.F5.WithCtrl;
+ Application.NextTabGroupKey = Key.F6;
+ Application.NextTabKey = Key.Tab;
+ Application.PrevTabGroupKey = Key.F6.WithShift;
+ Application.PrevTabKey = Key.Tab.WithShift;
+
// Always reset the model tracking to allow tests to use either model after reset
ResetModelUsageTracking ();
}