From 6d7c39fc1500b1736ceb0d515d03eb8db1fe6af8 Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Fri, 22 Jul 2022 15:13:18 +1200 Subject: [PATCH] Exit support (#155) - `IConfigContext` and `ConfigContext` can now be shut down. - Added the events `IConfigContext.Exit` and `IConfigContext.Exiting`. - Added a command to `Exit` from the `IConfigContext`. - Renamed all `disposedValue` variables to `_disposedValue`. - Updated the following to be `IDisposable`: - `CommandPalettePlugin` - `FocusIndicatorPlugin` - `IWorkspace` - `IWorkspaceManager` - `Logger` - `SwitchWorkspaceCommand` - `TreeLayoutEngineWidget` - `WorkspaceWidget` - Unsubscribed to `IConfigContext` and `IWindow` events in `Dispose` methods. - `Whim.Runner.App` now starts the `Whim` engine, instead of taking it as an argument from `Program.Main`. - `Whim.Runner.App` now responds to the `IConfigContext` having completed exiting. - Moved the loading and provisioning of the config from `Whim.Runner` to `Whim.ConfigContext.Initialize`. - `DoConfig` no longer returns `IConfigContext`. --- omnisharp.json | 2 +- .../ActiveLayoutWidgetViewModel.cs | 6 +- src/Whim.Bar/BarPlugin.cs | 7 +- src/Whim.Bar/BarWindow.xaml.cs | 2 - .../DateTime/DateTimeWidgetViewModel.cs | 7 +- .../FocusedWindowWidgetViewModel.cs | 6 +- .../Workspace/SwitchWorkspaceCommand.cs | 35 +++++- .../Workspace/WorkspaceWidget.xaml.cs | 32 ++++- .../Workspace/WorkspaceWidgetViewModel.cs | 6 +- .../CommandPalettePlugin.cs | 29 ++++- .../FocusIndicatorPlugin.cs | 36 +++++- src/Whim.Runner/App.xaml.cs | 62 ++++++---- src/Whim.Runner/ConfigHelper.cs | 84 ------------- src/Whim.Runner/Program.cs | 17 ++- src/Whim.Runner/RunnerException.cs | 17 --- src/Whim.Runner/StartupExceptionWindow.xaml | 2 +- .../StartupExceptionWindow.xaml.cs | 12 +- src/Whim.Runner/Whim.Runner.csproj | 11 +- src/Whim.Runner/Whim.cs | 62 ---------- src/Whim.Tests/WorkspaceManagerTests.cs | 26 ++-- .../TreeLayoutEngineWidget.xaml.cs | 46 ++++--- .../TreeLayoutEngineWidgetViewModel.cs | 14 ++- src/Whim/Commands/CommandManager.cs | 8 +- src/Whim/Commands/DefaultCommands.cs | 10 ++ src/Whim/Commands/KeybindManager.cs | 6 +- src/Whim/ConfigContext/ConfigContext.cs | 90 ++++++++------ .../{ShutdownEvent.cs => ExitEventArgs.cs} | 8 +- .../{ShutdownReason.cs => ExitReason.cs} | 2 +- src/Whim/ConfigContext/IConfigContext.cs | 20 ++- src/Whim/ConfigLoader/ConfigLoader.cs | 115 ++++++++++++++++++ .../ConfigLoader/ConfigLoaderException.cs | 24 ++++ src/Whim/Engine.cs | 15 ++- src/Whim/GlobalSuppressions.cs | 1 + src/Whim/Logging/Logger.cs | 33 ++++- src/Whim/Monitor/MonitorManager.cs | 8 +- src/Whim/Plugin/PluginManager.cs | 7 +- .../Template/omnisharp.json | 0 .../Template/whim.config.csx} | 5 +- src/Whim/Whim.csproj | 6 + src/Whim/Window/WindowManager.cs | 8 +- src/Whim/Workspace/IWorkspace.cs | 3 +- src/Whim/Workspace/IWorkspaceManager.cs | 2 +- src/Whim/Workspace/Workspace.cs | 35 ++++++ src/Whim/Workspace/WorkspaceManager.cs | 31 +++++ 44 files changed, 634 insertions(+), 324 deletions(-) delete mode 100644 src/Whim.Runner/ConfigHelper.cs delete mode 100644 src/Whim.Runner/RunnerException.cs delete mode 100644 src/Whim.Runner/Whim.cs rename src/Whim/ConfigContext/{ShutdownEvent.cs => ExitEventArgs.cs} (72%) rename src/Whim/ConfigContext/{ShutdownReason.cs => ExitReason.cs} (93%) create mode 100644 src/Whim/ConfigLoader/ConfigLoader.cs create mode 100644 src/Whim/ConfigLoader/ConfigLoaderException.cs rename src/{Whim.Runner => Whim}/Template/omnisharp.json (100%) rename src/{Whim.Runner/Template/whim.config.template.csx => Whim/Template/whim.config.csx} (96%) diff --git a/omnisharp.json b/omnisharp.json index 052513e21..7e3043a3f 100644 --- a/omnisharp.json +++ b/omnisharp.json @@ -1,7 +1,7 @@ { "FileOptions": { "ExcludeSearchPatterns": [ - "src/Whim.Runner/Template/whim.config.template.csx" + "src/Whim.Runner/Template/whim.config.csx" ] }, "RoslynExtensionsOptions": { diff --git a/src/Whim.Bar/ActiveLayout/ActiveLayoutWidgetViewModel.cs b/src/Whim.Bar/ActiveLayout/ActiveLayoutWidgetViewModel.cs index 03134dac4..399187241 100644 --- a/src/Whim.Bar/ActiveLayout/ActiveLayoutWidgetViewModel.cs +++ b/src/Whim.Bar/ActiveLayout/ActiveLayoutWidgetViewModel.cs @@ -16,7 +16,7 @@ public class ActiveLayoutWidgetViewModel : INotifyPropertyChanged, IDisposable /// public IMonitor Monitor { get; } - private bool disposedValue; + private bool _disposedValue; private readonly HashSet _workspaces = new(); @@ -62,7 +62,7 @@ public ActiveLayoutWidgetViewModel(IConfigContext configContext, IMonitor monito /// protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { @@ -74,7 +74,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim.Bar/BarPlugin.cs b/src/Whim.Bar/BarPlugin.cs index 49c2ecc0f..c25ba7754 100644 --- a/src/Whim.Bar/BarPlugin.cs +++ b/src/Whim.Bar/BarPlugin.cs @@ -13,7 +13,7 @@ public class BarPlugin : IPlugin, IDisposable private readonly BarConfig _barConfig; private readonly Dictionary _monitorBarMap = new(); - private bool disposedValue; + private bool _disposedValue; /// /// Create the bar plugin. @@ -81,7 +81,7 @@ private void ShowAll() /// protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { @@ -91,11 +91,12 @@ protected virtual void Dispose(bool disposing) } _monitorBarMap.Clear(); + _configContext.MonitorManager.MonitorsChanged -= MonitorManager_MonitorsChanged; } // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim.Bar/BarWindow.xaml.cs b/src/Whim.Bar/BarWindow.xaml.cs index 86a37e767..3517e8746 100644 --- a/src/Whim.Bar/BarWindow.xaml.cs +++ b/src/Whim.Bar/BarWindow.xaml.cs @@ -59,6 +59,4 @@ public BarWindow(IConfigContext configContext, BarConfig barConfig, IMonitor mon CenterPanel.Children.AddRange(_barConfig.CenterComponents.Select(c => c(_configContext, _monitor, this))); RightPanel.Children.AddRange(_barConfig.RightComponents.Select(c => c(_configContext, _monitor, this))); } - - } diff --git a/src/Whim.Bar/DateTime/DateTimeWidgetViewModel.cs b/src/Whim.Bar/DateTime/DateTimeWidgetViewModel.cs index d955332fc..7ea68b1ad 100644 --- a/src/Whim.Bar/DateTime/DateTimeWidgetViewModel.cs +++ b/src/Whim.Bar/DateTime/DateTimeWidgetViewModel.cs @@ -11,7 +11,7 @@ public class DateTimeWidgetViewModel : INotifyPropertyChanged, IDisposable { private readonly DispatcherTimer _timer = new(); private readonly string _format; - private bool disposedValue; + private bool _disposedValue; /// /// The current date and time. @@ -48,17 +48,18 @@ private void Timer_Tick(object? sender, object e) /// protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { // dispose managed state (managed objects) _timer.Stop(); + _timer.Tick -= Timer_Tick; } // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim.Bar/FocusedWindow/FocusedWindowWidgetViewModel.cs b/src/Whim.Bar/FocusedWindow/FocusedWindowWidgetViewModel.cs index 36c4067bd..647f7078a 100644 --- a/src/Whim.Bar/FocusedWindow/FocusedWindowWidgetViewModel.cs +++ b/src/Whim.Bar/FocusedWindow/FocusedWindowWidgetViewModel.cs @@ -9,7 +9,7 @@ namespace Whim.Bar; public class FocusedWindowWidgetViewModel : INotifyPropertyChanged, IDisposable { private readonly IConfigContext _configContext; - private bool disposedValue; + private bool _disposedValue; /// /// The title of the last focused window. @@ -38,7 +38,7 @@ protected virtual void OnPropertyChanged(string propertyName) /// protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { @@ -48,7 +48,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim.Bar/Workspace/SwitchWorkspaceCommand.cs b/src/Whim.Bar/Workspace/SwitchWorkspaceCommand.cs index 354336993..59f15faf6 100644 --- a/src/Whim.Bar/Workspace/SwitchWorkspaceCommand.cs +++ b/src/Whim.Bar/Workspace/SwitchWorkspaceCommand.cs @@ -6,11 +6,12 @@ namespace Whim.Bar; /// /// Command for switching workspace. /// -public class SwitchWorkspaceCommand : System.Windows.Input.ICommand +public class SwitchWorkspaceCommand : System.Windows.Input.ICommand, IDisposable { private readonly IConfigContext _configContext; private readonly WorkspaceWidgetViewModel _viewModel; private readonly WorkspaceModel _workspace; + private bool _disposedValue; /// public event EventHandler? CanExecuteChanged; @@ -50,4 +51,36 @@ public void Execute(object? parameter) _configContext.WorkspaceManager.Activate(_workspace.Workspace, _viewModel.Monitor); } } + + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + _workspace.PropertyChanged -= Workspace_PropertyChanged; + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + // // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~SwitchWorkspaceCommand() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs b/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs index 164456dcf..ab3b87a35 100644 --- a/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs +++ b/src/Whim.Bar/Workspace/WorkspaceWidget.xaml.cs @@ -1,12 +1,16 @@ using Microsoft.UI.Xaml.Controls; +using System; namespace Whim.Bar; /// /// Interaction logic for WorkspaceWidget.xaml /// -public partial class WorkspaceWidget : UserControl +public partial class WorkspaceWidget : UserControl, IDisposable { + private readonly Microsoft.UI.Xaml.Window _window; + private bool _disposedValue; + /// /// The workspace view model. /// @@ -14,6 +18,7 @@ public partial class WorkspaceWidget : UserControl internal WorkspaceWidget(IConfigContext configContext, IMonitor monitor, Microsoft.UI.Xaml.Window window) { + _window = window; ViewModel = new WorkspaceWidgetViewModel(configContext, monitor); window.Closed += Window_Closed; UIElementExtensions.InitializeComponent(this, "Whim.Bar", "Workspace/WorkspaceWidget"); @@ -31,4 +36,29 @@ public static BarComponent CreateComponent() { return new BarComponent((configContext, monitor, window) => new WorkspaceWidget(configContext, monitor, window)); } + + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + _window.Closed -= Window_Closed; + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs index a91c346eb..f3be0d1d3 100644 --- a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs +++ b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs @@ -11,7 +11,7 @@ namespace Whim.Bar; public class WorkspaceWidgetViewModel : INotifyPropertyChanged, IDisposable { private readonly IConfigContext _configContext; - private bool disposedValue; + private bool _disposedValue; /// /// The monitor which contains the workspaces. @@ -102,7 +102,7 @@ private void WorkspaceManager_MonitorWorkspaceChanged(object? sender, MonitorWor /// protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { @@ -114,7 +114,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim.CommandPalette/CommandPalettePlugin.cs b/src/Whim.CommandPalette/CommandPalettePlugin.cs index c562ae9aa..74433d5b2 100644 --- a/src/Whim.CommandPalette/CommandPalettePlugin.cs +++ b/src/Whim.CommandPalette/CommandPalettePlugin.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Whim.CommandPalette; @@ -6,10 +7,11 @@ namespace Whim.CommandPalette; /// CommandPalettePlugin displays a rudimentary command palette window. It allows the user to /// interact with the loaded commands of Whim. /// -public class CommandPalettePlugin : IPlugin +public class CommandPalettePlugin : IPlugin, IDisposable { private readonly IConfigContext _configContext; private CommandPaletteWindow? _commandPaletteWindow; + private bool _disposedValue; /// /// The configuration for the command palette plugin. @@ -67,4 +69,29 @@ public void Toggle() { _commandPaletteWindow?.Toggle(); } + + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + _commandPaletteWindow?.Close(); + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim.FocusIndicator/FocusIndicatorPlugin.cs b/src/Whim.FocusIndicator/FocusIndicatorPlugin.cs index 14b4dbc0b..f1ca34465 100644 --- a/src/Whim.FocusIndicator/FocusIndicatorPlugin.cs +++ b/src/Whim.FocusIndicator/FocusIndicatorPlugin.cs @@ -1,4 +1,5 @@ using Microsoft.UI.Xaml; +using System; using System.ComponentModel; namespace Whim.FocusIndicator; @@ -6,12 +7,13 @@ namespace Whim.FocusIndicator; /// /// FocusIndicatorPlugin is the plugin that displays a focus indicator on the focused window. /// -public class FocusIndicatorPlugin : IPlugin +public class FocusIndicatorPlugin : IPlugin, IDisposable { private readonly IConfigContext _configContext; private readonly FocusIndicatorConfig _focusIndicatorConfig; private FocusIndicatorWindow? _focusIndicatorWindow; private DispatcherTimer? _dispatcherTimer; + private bool _disposedValue; /// /// Creates a new instance of the focus indicator plugin. @@ -129,4 +131,36 @@ private void Hide() _dispatcherTimer.Tick -= DispatcherTimer_Tick; } } + + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + _configContext.WindowManager.WindowFocused -= WindowManager_WindowFocused; + _configContext.WindowManager.WindowRegistered -= WindowManager_EventSink; + _configContext.WindowManager.WindowUnregistered -= WindowManager_EventSink; + _configContext.WindowManager.WindowMoveStart -= WindowManager_EventSink; + _configContext.WindowManager.WindowMoved -= WindowManager_EventSink; + _configContext.WindowManager.WindowMinimizeStart -= WindowManager_EventSink; + _configContext.WindowManager.WindowMinimizeEnd -= WindowManager_EventSink; + _focusIndicatorWindow?.Close(); + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim.Runner/App.xaml.cs b/src/Whim.Runner/App.xaml.cs index 8ab3b9f6b..4f656a2ea 100644 --- a/src/Whim.Runner/App.xaml.cs +++ b/src/Whim.Runner/App.xaml.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml; using System; +using System.Reflection; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -11,15 +12,15 @@ namespace Whim.Runner; /// public partial class App : Application { - private readonly IConfigContext _configContext; - private Exception? _startupException; + /// + /// This will be initialized in . + /// + private IConfigContext? _configContext; /// /// Initializes the Whim application. /// - /// The Whim config context. - /// An exception encountered during startup. - public App(IConfigContext configContext, Exception? startupException = null) + public App() { Logger.Debug("Starting application..."); @@ -27,36 +28,53 @@ public App(IConfigContext configContext, Exception? startupException = null) UnhandledException += Application_UnhandledException; InitializeComponent(); - - _configContext = configContext; - _startupException = startupException; } /// protected override void OnLaunched(LaunchActivatedEventArgs args) { - if (_startupException == null) + StartWhim(); + } + + private void StartWhim() + { + try + { + _configContext = Engine.CreateConfigContext(Assembly.GetAssembly(typeof(Program))); + + _configContext.Exited += ConfigContext_Exited; + _configContext.Initialize(); + + return; + } + catch (Exception ex) { - try - { - _configContext.Initialize(); - return; - } - catch (Exception ex) - { - _startupException = ex; - } + _configContext?.Exit(new ExitEventArgs(ExitReason.Error, ex.ToString())); + } + } + + private void ConfigContext_Exited(object? sender, ExitEventArgs e) + { + if (_configContext is not null) + { + _configContext.Exited -= ConfigContext_Exited; + _configContext = null; } - // If we get to here, there's been an error somewhere during startup. - _configContext.Quit(); - new StartupExceptionWindow(_startupException!).Activate(); + if (e.Reason == ExitReason.User) + { + Exit(); + } + else + { + new StartupExceptionWindow(e).Activate(); + } } private void Application_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { Logger.Error(e.Exception.ToString()); - _configContext.Quit(); + _configContext?.Exit(); } // Add when Windows App SDK supports the application exit event. diff --git a/src/Whim.Runner/ConfigHelper.cs b/src/Whim.Runner/ConfigHelper.cs deleted file mode 100644 index 7721e80d0..000000000 --- a/src/Whim.Runner/ConfigHelper.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.IO; -using System.Linq; -using System.Reflection; - -namespace Whim.Runner; - -internal static class ConfigHelper -{ - private static readonly string configFilePath = FileHelper.GetWhimFileDir("whim.config.csx"); - - internal static bool DoesConfigExist() => File.Exists(configFilePath); - - /// - /// Loads the Whim config from the Whim config file, if it exists. - /// Otherwise, it will create a new Whim config file. - /// - /// The Whim config. - internal static string LoadConfig() => File.ReadAllText(configFilePath); - - /// - /// Read the given from the assembly's resources and return it as a string. - /// - /// - /// - /// - /// - private static string ReadFile(this Assembly assembly, string filename) - { - string? templateName = assembly.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith(filename)); - if (templateName == null) - { - throw new RunnerException($"Could not find file \"{filename}\" in assembly {assembly.FullName}"); - } - - using Stream? stream = assembly.GetManifestResourceStream(templateName); - if (stream == null) - { - throw new RunnerException($"Could not find manifest resource stream for \"{filename}\" in assembly {assembly.FullName}"); - } - - using StreamReader reader = new(stream); - return reader.ReadToEnd(); - } - - /// - /// Load the Whim template from the assembly's resources, and replace the WHIM_PATH. - /// - /// The Whim template. - private static string ReadConfigFile(this Assembly assembly) - { - // Load the Whim template from the assembly's resources. - string template = assembly.ReadFile("whim.config.template.csx"); - - // Replace WHIM_PATH with the assembly's path. - string? assemblyPath = Path.GetDirectoryName(assembly.Location); - if (assemblyPath == null) - { - throw new RunnerException($"Could not find assembly path for assembly {assembly.FullName}"); - } - - return template.Replace("WHIM_PATH", assemblyPath); - } - - /// - /// Creates a config based on the Whim template and saves it to the config file. - /// This will throw if any null values are encountered. - /// - internal static void CreateConfig() - { - // Load the assembly. - Assembly? assembly = Assembly.GetAssembly(typeof(Program)); - if (assembly == null) - { - throw new RunnerException($"Could not find assembly for {nameof(Program)}"); - } - - string template = assembly.ReadConfigFile(); - string omnisharpJson = assembly.ReadFile("omnisharp.json"); - - // Save the files. - File.WriteAllText(configFilePath, template); - File.WriteAllText(FileHelper.GetWhimFileDir("omnisharp.json"), omnisharpJson); - } -} diff --git a/src/Whim.Runner/Program.cs b/src/Whim.Runner/Program.cs index 0892b51eb..520e8ca57 100644 --- a/src/Whim.Runner/Program.cs +++ b/src/Whim.Runner/Program.cs @@ -1,6 +1,12 @@ -namespace Whim.Runner; +using Microsoft.UI.Dispatching; +using System.Threading; + +namespace Whim.Runner; #if DISABLE_XAML_GENERATED_MAIN +/// +/// This is the entry point for Whim. +/// public static class Program { [global::System.Runtime.InteropServices.DllImport("Microsoft.ui.xaml.dll")] @@ -45,7 +51,14 @@ static void Main(string[] args) if (this_is_the_first_instance) { - Whim.Start(); + System.Diagnostics.Debug.WriteLine("Starting!!!"); + // Start the application and the message loop. + global::Microsoft.UI.Xaml.Application.Start((p) => + { + DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + _ = new App(); + }); } } } diff --git a/src/Whim.Runner/RunnerException.cs b/src/Whim.Runner/RunnerException.cs deleted file mode 100644 index 721a56af9..000000000 --- a/src/Whim.Runner/RunnerException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Whim.Runner; - -/// -/// Exception thrown by Whim.Runner. -/// -[Serializable] -public class RunnerException : Exception -{ - public RunnerException() { } - public RunnerException(string message) : base(message) { } - public RunnerException(string message, Exception inner) : base(message, inner) { } - protected RunnerException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -} diff --git a/src/Whim.Runner/StartupExceptionWindow.xaml b/src/Whim.Runner/StartupExceptionWindow.xaml index 16b7bbabc..ed33ac733 100644 --- a/src/Whim.Runner/StartupExceptionWindow.xaml +++ b/src/Whim.Runner/StartupExceptionWindow.xaml @@ -24,7 +24,7 @@ FontFamily="Cascadia Code" IsTextSelectionEnabled="True" LineHeight="20" - Text="{x:Bind Exception}" + Text="{x:Bind Message}" TextWrapping="WrapWholeWords" /> diff --git a/src/Whim.Runner/StartupExceptionWindow.xaml.cs b/src/Whim.Runner/StartupExceptionWindow.xaml.cs index f8971b0e2..3cf70e7a5 100644 --- a/src/Whim.Runner/StartupExceptionWindow.xaml.cs +++ b/src/Whim.Runner/StartupExceptionWindow.xaml.cs @@ -1,5 +1,4 @@ using Microsoft.UI.Xaml; -using System; namespace Whim.Runner; @@ -8,18 +7,21 @@ namespace Whim.Runner; /// public sealed partial class StartupExceptionWindow : Microsoft.UI.Xaml.Window { - public string Exception { get; } + /// + /// The exception message. + /// + public string Message { get; } /// /// Exposes the exception encountered during startup to the user. /// - /// - public StartupExceptionWindow(Exception exception) + /// + public StartupExceptionWindow(ExitEventArgs exitEventArgs) { InitializeComponent(); Title = "Whim Startup Error"; - Exception = exception.ToString(); + Message = exitEventArgs.Message ?? "Unknown error occurred"; } private void Quit_Click(object sender, RoutedEventArgs e) diff --git a/src/Whim.Runner/Whim.Runner.csproj b/src/Whim.Runner/Whim.Runner.csproj index 483deb040..27c923d1a 100644 --- a/src/Whim.Runner/Whim.Runner.csproj +++ b/src/Whim.Runner/Whim.Runner.csproj @@ -72,11 +72,6 @@ - - - - - @@ -109,6 +104,12 @@ + + + + + + diff --git a/src/Whim.Runner/Whim.cs b/src/Whim.Runner/Whim.cs deleted file mode 100644 index d08ccd0e1..000000000 --- a/src/Whim.Runner/Whim.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Scripting; -using Microsoft.CodeAnalysis.Scripting; -using Microsoft.UI.Dispatching; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Whim.Runner; - -internal class Whim -{ - internal static void Start() - { - // Create an empty Whim config context. - IConfigContext configContext = Engine.CreateConfigContext(); - Exception? startupException = null; - - try - { - configContext = GetConfigContext(configContext); - } - catch (Exception ex) - { - startupException = ex; - } - - // Start the application and the message loop. - global::Microsoft.UI.Xaml.Application.Start((p) => - { - DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread()); - SynchronizationContext.SetSynchronizationContext(context); - _ = new App(configContext, startupException); - }); - } - - /// - /// Acquires and evaluates the user's . - /// - /// - /// - private static IConfigContext GetConfigContext(IConfigContext configContext) - { - // Ensure the Whim directory exists. - FileHelper.EnsureWhimDirExists(); - - // Acquire the Whim config. - bool configExists = ConfigHelper.DoesConfigExist(); - if (!configExists) - { - ConfigHelper.CreateConfig(); - } - - string rawConfig = ConfigHelper.LoadConfig(); - - // Evaluate the Whim config. - ScriptOptions options = ScriptOptions.Default; - Task> task = CSharpScript.EvaluateAsync>(rawConfig, options); - Func doConfig = task.Result; - - return doConfig(configContext); - } -} diff --git a/src/Whim.Tests/WorkspaceManagerTests.cs b/src/Whim.Tests/WorkspaceManagerTests.cs index d06b0a8d4..19b592580 100644 --- a/src/Whim.Tests/WorkspaceManagerTests.cs +++ b/src/Whim.Tests/WorkspaceManagerTests.cs @@ -31,7 +31,7 @@ private static (Mock, Mock, Mock) CreateMon public void AddWorkspace() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Assert.Raises(h => workspaceManager.WorkspaceAdded += h, h => workspaceManager.WorkspaceAdded -= h, @@ -47,7 +47,7 @@ public void AddWorkspace() public void RemoveUnknownWorkspace() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Assert.False(workspaceManager.Remove(new Mock().Object)); } @@ -56,7 +56,7 @@ public void RemoveUnknownWorkspace() public void RemoveTooManyWorkspaces() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Assert.False(workspaceManager.Remove(new Mock().Object)); } @@ -65,7 +65,7 @@ public void RemoveTooManyWorkspaces() public void RemoveWorkspaceNotInList() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Assert.False(workspaceManager.Remove(new Mock().Object)); } @@ -74,7 +74,7 @@ public void RemoveWorkspaceNotInList() public void RemoveWorkspace() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Mock windowMock = new(); @@ -106,7 +106,7 @@ public void RemoveWorkspace() public void RemoveWorkspaceStringFail() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Assert.False(workspaceManager.Remove("Test")); } @@ -115,7 +115,7 @@ public void RemoveWorkspaceStringFail() public void RemoveWorkspaceString() { Mock configContextMock = CreateConfigContextMock(); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); Mock windowMock = new(); @@ -146,7 +146,7 @@ public void Activate() configContextMock.Setup(x => x.MonitorManager).Returns(monitorManagerMock.Object); // Setup workspace manager - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); // Setup workspace manager workspaces Mock workspace = new(); @@ -196,7 +196,7 @@ public void GetMonitorForWorkspaceNull() Mock configContextMock = CreateConfigContextMock(); configContextMock.Setup(x => x.MonitorManager).Returns(monitorManagerMock.Object); - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); // Register the monitor in _monitorWorkspaceMap workspaceManager.Activate(new Mock().Object, monitorMock.Object); @@ -216,7 +216,7 @@ public void LayoutAllActiveWorkspaces() configContextMock.Setup(x => x.MonitorManager).Returns(monitorManagerMock.Object); // Setup workspace manager - WorkspaceManager workspaceManager = new(configContextMock.Object); + using WorkspaceManager workspaceManager = new(configContextMock.Object); // Setup workspace manager workspaces Mock workspace = new(); @@ -259,7 +259,7 @@ public void MoveWindowToWorkspace() configContextMock.Setup(x => x.WindowManager).Returns(windowManagerMock.Object); configContextMock.Setup(x => x.MonitorManager).Returns(monitorManagerMock.Object); - WorkspaceManager workspaceManager = new(configContextMock.Object) + using WorkspaceManager workspaceManager = new(configContextMock.Object) { workspaceMock.Object }; @@ -294,7 +294,7 @@ public void MoveWindowToWorkspace_NullWindow() configContextMock.Setup(x => x.WindowManager).Returns(windowManagerMock.Object); configContextMock.Setup(x => x.MonitorManager).Returns(monitorManagerMock.Object); - WorkspaceManager workspaceManager = new(configContextMock.Object) + using WorkspaceManager workspaceManager = new(configContextMock.Object) { workspaceMock.Object }; @@ -325,7 +325,7 @@ public void MoveWindowToWorkspace_ActiveWorkspace() configContextMock.Setup(x => x.WindowManager).Returns(windowManagerMock.Object); configContextMock.Setup(x => x.MonitorManager).Returns(monitorManagerMock.Object); - WorkspaceManager workspaceManager = new(configContextMock.Object) + using WorkspaceManager workspaceManager = new(configContextMock.Object) { workspaceMock.Object }; diff --git a/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidget.xaml.cs b/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidget.xaml.cs index a741083de..7e55ff4a5 100644 --- a/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidget.xaml.cs +++ b/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidget.xaml.cs @@ -1,26 +1,18 @@ -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; +using Microsoft.UI.Xaml.Controls; using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Whim.Bar; -using Windows.Foundation; -using Windows.Foundation.Collections; namespace Whim.TreeLayout.Bar; /// /// Interaction logic for TreeLayoutEngineWidget. /// -public sealed partial class TreeLayoutEngineWidget : UserControl +public sealed partial class TreeLayoutEngineWidget : UserControl, IDisposable { + private bool _disposedValue; + + private readonly Microsoft.UI.Xaml.Window _window; + /// /// The tree layout engine widget. /// @@ -28,6 +20,7 @@ public sealed partial class TreeLayoutEngineWidget : UserControl internal TreeLayoutEngineWidget(IConfigContext configContext, IMonitor monitor, Microsoft.UI.Xaml.Window window) { + _window = window; ViewModel = new TreeLayoutEngineWidgetViewModel(configContext, monitor); window.Closed += Window_Closed; UIElementExtensions.InitializeComponent(this, "Whim.TreeLayout.Bar", "TreeLayoutEngineWidget"); @@ -45,4 +38,29 @@ public static BarComponent CreateComponent() { return new BarComponent((configContext, monitor, window) => new TreeLayoutEngineWidget(configContext, monitor, window)); } + + /// + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + _window.Closed -= Window_Closed; + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidgetViewModel.cs b/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidgetViewModel.cs index f300b5c70..2752f4033 100644 --- a/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidgetViewModel.cs +++ b/src/Whim.TreeLayout.Bar/TreeLayoutEngineWidgetViewModel.cs @@ -11,7 +11,7 @@ public class TreeLayoutEngineWidgetViewModel : INotifyPropertyChanged, IDisposab { private readonly IConfigContext _configContext; private readonly IMonitor _monitor; - private bool disposedValue; + private bool _disposedValue; private Direction? _directionValue; @@ -149,16 +149,18 @@ protected virtual void OnPropertyChanged(string? propertyName) /// protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { - // TODO: dispose managed state (managed objects) + // dispose managed state (managed objects) + _configContext.WorkspaceManager.MonitorWorkspaceChanged -= WorkspaceManager_MonitorWorkspaceChanged; + _configContext.WorkspaceManager.ActiveLayoutEngineChanged -= WorkspaceManager_ActiveLayoutEngineChanged; } - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - disposedValue = true; + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; } } diff --git a/src/Whim/Commands/CommandManager.cs b/src/Whim/Commands/CommandManager.cs index 3f0aa5863..70fda6f4f 100644 --- a/src/Whim/Commands/CommandManager.cs +++ b/src/Whim/Commands/CommandManager.cs @@ -8,7 +8,7 @@ internal class CommandManager : ICommandManager { private readonly ICommandItems _commandItems; private readonly IKeybindManager _keybindManager; - private bool disposedValue; + private bool _disposedValue; public CommandManager() { @@ -34,17 +34,19 @@ public void Initialize() protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { + Logger.Debug("Disposing command manager"); + // dispose managed state (managed objects) _keybindManager.Dispose(); } // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim/Commands/DefaultCommands.cs b/src/Whim/Commands/DefaultCommands.cs index 4cb9f50a9..747c78f80 100644 --- a/src/Whim/Commands/DefaultCommands.cs +++ b/src/Whim/Commands/DefaultCommands.cs @@ -113,6 +113,16 @@ public static class DefaultCommands ), new Keybind(WinShift, VIRTUAL_KEY.VK_RIGHT) ), + + // Exit. + ( + new Command( + identifier: "default_commands.exit", + title: "Exit Whim", + callback: () => configContext.Exit() + ), + null + ), }; /// diff --git a/src/Whim/Commands/KeybindManager.cs b/src/Whim/Commands/KeybindManager.cs index 151827ae8..b4d429ad2 100644 --- a/src/Whim/Commands/KeybindManager.cs +++ b/src/Whim/Commands/KeybindManager.cs @@ -12,7 +12,7 @@ internal class KeybindManager : IKeybindManager private readonly ICommandItems _commandItems; private readonly HOOKPROC _keyboardHook; private UnhookWindowsHookExSafeHandle? _unhookKeyboardHook; - private bool disposedValue; + private bool _disposedValue; public KeybindManager(ICommandItems commandItems) { @@ -136,7 +136,7 @@ private bool DoKeyboardEvent(Keybind keybind) protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { @@ -149,7 +149,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim/ConfigContext/ConfigContext.cs b/src/Whim/ConfigContext/ConfigContext.cs index 63cfabbc9..66bf7ba23 100644 --- a/src/Whim/ConfigContext/ConfigContext.cs +++ b/src/Whim/ConfigContext/ConfigContext.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Whim; @@ -13,43 +14,51 @@ namespace Whim; /// internal class ConfigContext : IConfigContext { - public Logger Logger { get; set; } - public IWorkspaceManager WorkspaceManager { get; set; } - public IWindowManager WindowManager { get; set; } - public IMonitorManager MonitorManager { get; set; } - public IRouterManager RouterManager { get; set; } - public IFilterManager FilterManager { get; set; } - public ICommandManager CommandManager { get; set; } - public IPluginManager PluginManager { get; set; } - - public event EventHandler? Shutdown; - - public ConfigContext( - Logger? logger = null, - IRouterManager? routerManager = null, - IFilterManager? filterManager = null, - IWindowManager? windowManager = null, - IMonitorManager? monitorManager = null, - IWorkspaceManager? workspaceManager = null, - ICommandManager? commandManager = null, - IPluginManager? pluginManager = null) + /// + /// The assembly which is running this context. + /// + private readonly Assembly _assembly; + + public Logger Logger { get; private set; } + public IWorkspaceManager WorkspaceManager { get; private set; } + public IWindowManager WindowManager { get; private set; } + public IMonitorManager MonitorManager { get; private set; } + public IRouterManager RouterManager { get; private set; } + public IFilterManager FilterManager { get; private set; } + public ICommandManager CommandManager { get; private set; } + public IPluginManager PluginManager { get; private set; } + + public event EventHandler? Exiting; + public event EventHandler? Exited; + + /// + /// Create a new . + /// + /// The assembly which is running this context. + public ConfigContext(Assembly assembly) { - Logger = logger ?? new Logger(); - RouterManager = routerManager ?? new RouterManager(this); - FilterManager = filterManager ?? new FilterManager(); - WindowManager = windowManager ?? new WindowManager(this); - MonitorManager = monitorManager ?? new MonitorManager(this); - WorkspaceManager = workspaceManager ?? new WorkspaceManager(this); - CommandManager = commandManager ?? new CommandManager(); - PluginManager = pluginManager ?? new PluginManager(); + _assembly = assembly; + + Logger = new Logger(); + RouterManager = new RouterManager(this); + FilterManager = new FilterManager(); + WindowManager = new WindowManager(this); + MonitorManager = new MonitorManager(this); + WorkspaceManager = new WorkspaceManager(this); + CommandManager = new CommandManager(); + PluginManager = new PluginManager(); } public void Initialize() { - Logger.Initialize(); + // Load the config context. + DoConfig doConfig = ConfigLoader.LoadConfigContext(_assembly); + doConfig(this); - Logger.Debug("Initializing config context..."); + // Initialize the managers. + Logger.Initialize(); + Logger.Debug("Initializing..."); PluginManager.PreInitialize(); MonitorManager.Initialize(); @@ -59,17 +68,26 @@ public void Initialize() WindowManager.PostInitialize(); PluginManager.PostInitialize(); - } + Logger.Debug("Completed initialization"); + } - public void Quit(ShutdownEventArgs? args = null) + public void Exit(ExitEventArgs? args = null) { - Logger.Debug("Disposing config context..."); + Logger.Debug("Exiting config context..."); + args ??= new ExitEventArgs(ExitReason.User); + + Exiting?.Invoke(this, args); + + PluginManager.Dispose(); + CommandManager.Dispose(); + WorkspaceManager.Dispose(); WindowManager.Dispose(); MonitorManager.Dispose(); - CommandManager.Dispose(); - PluginManager.Dispose(); - Shutdown?.Invoke(this, args ?? new ShutdownEventArgs(ShutdownReason.User)); + Logger.Debug("Mostly exited..."); + + Logger.Dispose(); + Exited?.Invoke(this, args); } } diff --git a/src/Whim/ConfigContext/ShutdownEvent.cs b/src/Whim/ConfigContext/ExitEventArgs.cs similarity index 72% rename from src/Whim/ConfigContext/ShutdownEvent.cs rename to src/Whim/ConfigContext/ExitEventArgs.cs index 340c8cd48..cdf2f587c 100644 --- a/src/Whim/ConfigContext/ShutdownEvent.cs +++ b/src/Whim/ConfigContext/ExitEventArgs.cs @@ -3,14 +3,14 @@ namespace Whim; /// -/// Event arguments for the Shutdown event. +/// Event arguments for the event. /// -public class ShutdownEventArgs : EventArgs +public class ExitEventArgs : EventArgs { /// /// The reason why Whim has been shut down. /// - public ShutdownReason Reason { get; } + public ExitReason Reason { get; } /// /// A string describing the reason why Whim has been shut down. @@ -22,7 +22,7 @@ public class ShutdownEventArgs : EventArgs /// /// The reason why Whim has been shut down. /// A string describing the reason why Whim has been shut down. - public ShutdownEventArgs(ShutdownReason reason, string? message = null) + public ExitEventArgs(ExitReason reason, string? message = null) { Reason = reason; Message = message; diff --git a/src/Whim/ConfigContext/ShutdownReason.cs b/src/Whim/ConfigContext/ExitReason.cs similarity index 93% rename from src/Whim/ConfigContext/ShutdownReason.cs rename to src/Whim/ConfigContext/ExitReason.cs index 541cd837f..bac6ac644 100644 --- a/src/Whim/ConfigContext/ShutdownReason.cs +++ b/src/Whim/ConfigContext/ExitReason.cs @@ -3,7 +3,7 @@ namespace Whim; /// /// The possible reasons why Whim has been shut down. /// -public enum ShutdownReason +public enum ExitReason { /// /// Whim has been shut down normally. diff --git a/src/Whim/ConfigContext/IConfigContext.cs b/src/Whim/ConfigContext/IConfigContext.cs index 9ef051cad..6387f490d 100644 --- a/src/Whim/ConfigContext/IConfigContext.cs +++ b/src/Whim/ConfigContext/IConfigContext.cs @@ -9,7 +9,7 @@ namespace Whim; /// functionality.
/// /// IConfigContext also contains other associated state and functionality, like the -/// +/// . ///
public interface IConfigContext { @@ -54,22 +54,30 @@ public interface IConfigContext public IPluginManager PluginManager { get; } /// - /// This will be called by Whim after your config has been read. - /// Do not call it yourself. + /// This will be called by the Whim Runner. + /// You likely won't need to call it yourself. /// + /// + /// Thrown if the user's config could not be loaded. + /// public void Initialize(); /// /// This event is fired when the config context is shutting down. /// - public event EventHandler? Shutdown; + public event EventHandler? Exiting; + + /// + /// This event is fired after the config context has been shut down. + /// + public event EventHandler? Exited; /// /// This is called to shutdown the config context. /// /// /// The shutdown event arguments. If this is not provided, we assume - /// . + /// . /// - public void Quit(ShutdownEventArgs? args = null); + public void Exit(ExitEventArgs? args = null); } diff --git a/src/Whim/ConfigLoader/ConfigLoader.cs b/src/Whim/ConfigLoader/ConfigLoader.cs new file mode 100644 index 000000000..342ee5d23 --- /dev/null +++ b/src/Whim/ConfigLoader/ConfigLoader.cs @@ -0,0 +1,115 @@ +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Whim; + +/// +/// Applies the user's config to the . +/// +/// The to operate on. +public delegate void DoConfig(IConfigContext configContext); + +internal static class ConfigLoader +{ + private static readonly string ConfigFilePath = FileHelper.GetWhimFileDir("whim.config.csx"); + + private static bool DoesConfigExist() => File.Exists(ConfigFilePath); + + /// + /// Loads the Whim config from the Whim config file, if it exists. + /// Otherwise, it will create a new Whim config file. + /// + /// The Whim config. + private static string LoadRawConfig() => File.ReadAllText(ConfigFilePath); + + /// + /// Read the given from the assembly's resources and return it as a string. + /// + /// + /// + /// + /// + private static string ReadFile(this Assembly assembly, string filename) + { + string? templateName = assembly.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith(filename)); + if (templateName == null) + { + throw new ConfigLoaderException($"Could not find file \"{filename}\" in assembly {assembly.FullName}"); + } + + using Stream? stream = assembly.GetManifestResourceStream(templateName); + if (stream == null) + { + throw new ConfigLoaderException($"Could not find manifest resource stream for \"{filename}\" in assembly {assembly.FullName}"); + } + + using StreamReader reader = new(stream); + return reader.ReadToEnd(); + } + + /// + /// Load the Whim template from the assembly's resources, and replace the WHIM_PATH. + /// + /// The Whim template. + /// + private static string ReadTemplateConfigFile(this Assembly assembly) + { + // Load the Whim template from the assembly's resources. + string template = assembly.ReadFile("whim.config.csx"); + + // Replace WHIM_PATH with the assembly's path. + string? assemblyPath = Path.GetDirectoryName(assembly.Location); + if (assemblyPath == null) + { + throw new ConfigLoaderException($"Could not find assembly path for assembly {assembly.FullName}"); + } + + return template.Replace("WHIM_PATH", assemblyPath); + } + + /// + /// Creates a config based on the Whim template and saves it to the config file. + /// This will throw if any null values are encountered. + /// + /// The assembly from which to load. + /// + private static void CreateConfig(Assembly assembly) + { + string template = assembly.ReadTemplateConfigFile(); + string omnisharpJson = assembly.ReadFile("omnisharp.json"); + + // Save the files. + File.WriteAllText(ConfigFilePath, template); + File.WriteAllText(FileHelper.GetWhimFileDir("omnisharp.json"), omnisharpJson); + } + + /// + /// Acquires and evaluates the user's . + /// + /// + /// The . + /// + internal static DoConfig LoadConfigContext(Assembly assembly) + { + // Ensure the Whim directory exists. + FileHelper.EnsureWhimDirExists(); + + // Acquire the Whim config. + bool configExists = DoesConfigExist(); + if (!configExists) + { + CreateConfig(assembly); + } + + string rawConfig = LoadRawConfig(); + + // Evaluate the Whim config. + ScriptOptions options = ScriptOptions.Default; + Task task = CSharpScript.EvaluateAsync(rawConfig, options); + return task.Result; + } +} diff --git a/src/Whim/ConfigLoader/ConfigLoaderException.cs b/src/Whim/ConfigLoader/ConfigLoaderException.cs new file mode 100644 index 000000000..f1769c84d --- /dev/null +++ b/src/Whim/ConfigLoader/ConfigLoaderException.cs @@ -0,0 +1,24 @@ +using System; + +namespace Whim; + +/// +/// Exception thrown by Whim when loading the config. +/// +[Serializable] +public class ConfigLoaderException : Exception +{ + /// + public ConfigLoaderException() { } + + /// + public ConfigLoaderException(string message) : base(message) { } + + /// + public ConfigLoaderException(string message, Exception inner) : base(message, inner) { } + + /// + protected ConfigLoaderException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } +} diff --git a/src/Whim/Engine.cs b/src/Whim/Engine.cs index 17cc36039..a2ae87e79 100644 --- a/src/Whim/Engine.cs +++ b/src/Whim/Engine.cs @@ -1,3 +1,5 @@ +using System.Reflection; + namespace Whim; /// @@ -10,12 +12,21 @@ public static class Engine /// /// Get the . /// - public static IConfigContext CreateConfigContext() + /// The calling assembly. + /// The . + /// + public static IConfigContext CreateConfigContext(Assembly? assembly) { if (_configContext == null) { - _configContext = new ConfigContext(); + if (assembly == null) + { + throw new ConfigLoaderException("Provided assembly was null."); + } + + _configContext = new ConfigContext(assembly); } + return _configContext; } } diff --git a/src/Whim/GlobalSuppressions.cs b/src/Whim/GlobalSuppressions.cs index f32dbc246..9781374e9 100644 --- a/src/Whim/GlobalSuppressions.cs +++ b/src/Whim/GlobalSuppressions.cs @@ -10,3 +10,4 @@ [assembly: SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Will be resolved by #51", Scope = "module")] [assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "They are used by subclasses", Scope = "module")] [assembly: SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "They are used by subclasses", Scope = "module")] +[assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Not concerned about Visual Basic", Scope = "member", Target = "~M:Whim.IConfigContext.Exit(Whim.ExitEventArgs)")] diff --git a/src/Whim/Logging/Logger.cs b/src/Whim/Logging/Logger.cs index 23cc26628..cbadd3a09 100644 --- a/src/Whim/Logging/Logger.cs +++ b/src/Whim/Logging/Logger.cs @@ -1,4 +1,5 @@ using Serilog; +using System; using System.IO; using System.Runtime.CompilerServices; @@ -7,7 +8,7 @@ namespace Whim; /// /// Logger used throughout Whim. It is accessed according to the singleton pattern. /// -public class Logger +public class Logger : IDisposable { /// /// Logger instance. @@ -24,6 +25,8 @@ public class Logger /// private LoggerConfiguration? _loggerConfiguration; + private bool _disposedValue; + /// /// The config for the logger. /// NOTE: Changes to this will only take effect if set prior to . @@ -158,4 +161,32 @@ public static void Fatal(string message, [CallerMemberName] string memberName = { instance?._logger?.Fatal(AddCaller(message, memberName, sourceFilePath, sourceLineNumber)); } + + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + if (_logger is IDisposable disposable) + { + disposable.Dispose(); + } + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim/Monitor/MonitorManager.cs b/src/Whim/Monitor/MonitorManager.cs index 3a5b83127..4ca2b7f18 100644 --- a/src/Whim/Monitor/MonitorManager.cs +++ b/src/Whim/Monitor/MonitorManager.cs @@ -19,7 +19,7 @@ internal class MonitorManager : IMonitorManager /// zero s, as the constructor should throw. /// private IMonitor[] _monitors = Array.Empty(); - private bool disposedValue; + private bool _disposedValue; /// /// The which currently has focus. @@ -184,17 +184,19 @@ public IMonitor GetNextMonitor(IMonitor monitor) protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { + Logger.Debug("Disposing monitor manager"); + // dispose managed state (managed objects) SystemEvents.DisplaySettingsChanging -= SystemEvents_DisplaySettingsChanging; } // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } public void Dispose() diff --git a/src/Whim/Plugin/PluginManager.cs b/src/Whim/Plugin/PluginManager.cs index af8e9b070..99dbf9b97 100644 --- a/src/Whim/Plugin/PluginManager.cs +++ b/src/Whim/Plugin/PluginManager.cs @@ -6,7 +6,7 @@ namespace Whim; internal class PluginManager : IPluginManager { private readonly List _plugins = new(); - private bool disposedValue; + private bool _disposedValue; public IEnumerable LoadedPlugins => _plugins; @@ -38,10 +38,11 @@ public T RegisterPlugin(T plugin) where T : IPlugin protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { + Logger.Debug("Disposing plugin manager"); foreach (IPlugin plugin in _plugins) { if (plugin is IDisposable disposable) @@ -51,7 +52,7 @@ protected virtual void Dispose(bool disposing) } } - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim.Runner/Template/omnisharp.json b/src/Whim/Template/omnisharp.json similarity index 100% rename from src/Whim.Runner/Template/omnisharp.json rename to src/Whim/Template/omnisharp.json diff --git a/src/Whim.Runner/Template/whim.config.template.csx b/src/Whim/Template/whim.config.csx similarity index 96% rename from src/Whim.Runner/Template/whim.config.template.csx rename to src/Whim/Template/whim.config.csx index c52a7fa6b..0185ef2ad 100644 --- a/src/Whim.Runner/Template/whim.config.template.csx +++ b/src/Whim/Template/whim.config.csx @@ -30,8 +30,7 @@ IWorkspace CreateWorkspace(IConfigContext configContext, string name) /// This is what's called when Whim is loaded. /// /// -/// -IConfigContext DoConfig(IConfigContext configContext) +void DoConfig(IConfigContext configContext) { // Add workspaces. configContext.WorkspaceManager.Add(CreateWorkspace(configContext, "1")); @@ -64,8 +63,6 @@ IConfigContext DoConfig(IConfigContext configContext) // Load the commands and keybindings. configContext.CommandManager.LoadCommands(DefaultCommands.GetCommands(configContext)); configContext.CommandManager.LoadCommands(FloatingLayoutCommands.GetCommands(floatingLayoutPlugin)); - - return configContext; } #pragma warning disable CS8974 // Methods should not return 'this'. diff --git a/src/Whim/Whim.csproj b/src/Whim/Whim.csproj index b95478b8a..1a0108299 100644 --- a/src/Whim/Whim.csproj +++ b/src/Whim/Whim.csproj @@ -29,6 +29,7 @@ + all @@ -50,4 +51,9 @@ <_Parameter1>DynamicProxyGenAssembly2 + + + + + \ No newline at end of file diff --git a/src/Whim/Window/WindowManager.cs b/src/Whim/Window/WindowManager.cs index 4bd208da9..c8bb73e15 100644 --- a/src/Whim/Window/WindowManager.cs +++ b/src/Whim/Window/WindowManager.cs @@ -42,7 +42,7 @@ internal class WindowManager : IWindowManager /// /// Indicates whether values have been disposed. /// - private bool disposedValue; + private bool _disposedValue; public WindowManager(IConfigContext configContext) { @@ -83,10 +83,12 @@ public void PostInitialize() protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { + Logger.Debug("Disposing window manager"); + foreach (UnhookWinEventSafeHandle? hook in _registeredHooks) { if (hook == null || hook.IsClosed || hook.IsInvalid) @@ -100,7 +102,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/Whim/Workspace/IWorkspace.cs b/src/Whim/Workspace/IWorkspace.cs index 069376df4..373d97d25 100644 --- a/src/Whim/Workspace/IWorkspace.cs +++ b/src/Whim/Workspace/IWorkspace.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Whim; @@ -5,7 +6,7 @@ namespace Whim; /// /// Workspaces contain windows to be organized by layout engines. /// -public interface IWorkspace +public interface IWorkspace : IDisposable { /// /// The name of the workspace. When the Name is set, the diff --git a/src/Whim/Workspace/IWorkspaceManager.cs b/src/Whim/Workspace/IWorkspaceManager.cs index 8b96c98f3..95951a4e0 100644 --- a/src/Whim/Workspace/IWorkspaceManager.cs +++ b/src/Whim/Workspace/IWorkspaceManager.cs @@ -7,7 +7,7 @@ namespace Whim; /// The manager for s. This is responsible for routing /// windows between workspaces. /// -public interface IWorkspaceManager : IEnumerable +public interface IWorkspaceManager : IEnumerable, IDisposable { /// /// The active workspace. diff --git a/src/Whim/Workspace/Workspace.cs b/src/Whim/Workspace/Workspace.cs index f8ca91914..4c511c519 100644 --- a/src/Whim/Workspace/Workspace.cs +++ b/src/Whim/Workspace/Workspace.cs @@ -28,6 +28,7 @@ public string Name private readonly List _layoutEngines = new(); private int _activeLayoutEngineIndex; + private bool _disposedValue; public ILayoutEngine ActiveLayoutEngine => _layoutEngines[_activeLayoutEngineIndex]; @@ -468,4 +469,38 @@ public bool ContainsWindow(IWindow window) => _windows.Contains(window) || ( _phantomWindows.TryGetValue(window, out ILayoutEngine? phantomEngine) && ILayoutEngine.ContainsEqual(ActiveLayoutEngine, phantomEngine) ); + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + Logger.Debug($"Disposing workspace {Name}"); + + // dispose managed state (managed objects) + bool isWorkspaceActive = _configContext.WorkspaceManager.GetMonitorForWorkspace(this) != null; + + // If the workspace isn't active on the monitor, show all the windows in as minimized. + if (!isWorkspaceActive) + { + foreach (IWindow window in Windows) + { + window.ShowMinimized(); + } + } + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Whim/Workspace/WorkspaceManager.cs b/src/Whim/Workspace/WorkspaceManager.cs index 74ff9bb0e..db3ab3c1b 100644 --- a/src/Whim/Workspace/WorkspaceManager.cs +++ b/src/Whim/Workspace/WorkspaceManager.cs @@ -50,6 +50,9 @@ internal class WorkspaceManager : IWorkspaceManager public IWorkspace ActiveWorkspace => _monitorWorkspaceMap[_configContext.MonitorManager.FocusedMonitor]; private readonly List _proxyLayoutEngines = new(); + + private bool _disposedValue; + public IEnumerable ProxyLayoutEngines => _proxyLayoutEngines; public WorkspaceManager(IConfigContext configContext) @@ -453,5 +456,33 @@ public void UnregisterPhantomWindow(IWindow window) _phantomWindows.Remove(window); _windowWorkspaceMap.Remove(window); } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + Logger.Debug("Disposing workspace manager"); + + // dispose managed state (managed objects) + foreach (IWorkspace workspace in _workspaces) + { + workspace.Dispose(); + } + } + + // free unmanaged resources (unmanaged objects) and override finalizer + // set large fields to null + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } #endregion }