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 (); }