using System.Windows; using CursorLang.Core.Services; using CursorLang.Settings.Services; using CursorLang.Settings.ViewModels; using CursorLang.Settings.Views; using Microsoft.Extensions.DependencyInjection; namespace CursorLang.Settings; /// /// The settings window as a process of its own. /// /// /// It is started by the agent — from the tray menu, or straight away when the user /// launches the application themselves — and it ends when the window is closed. That /// is the whole point of the split: WPF costs around a hundred megabytes, and this way /// the system gets all of it back the moment the user is done, instead of the /// background process carrying it until sign-out. /// /// Nothing here talks to the agent directly. Every edit goes into settings.json, and /// nudges the agent to re-read it once the file is /// written. An agent that is not running is a normal case: the window works the same. /// // ReSharper disable once RedundantExtendsListEntry public partial class App : Application { private ServiceProvider? _services; private SingleInstanceGate? _instanceGate; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var gate = new SingleInstanceGate(SingleInstanceGate.SettingsName); if (!gate.TryAcquire(showRunningInstance: true)) { gate.Dispose(); Shutdown(); return; } _instanceGate = gate; _instanceGate.ActivationRequested += OnActivationRequested; var services = new ServiceCollection(); ConfigureServices(services); _services = services.BuildServiceProvider(); _services.GetRequiredService().TrackChanges(); _services.GetRequiredService(); MainWindow = _services.GetRequiredService(); ShutdownMode = ShutdownMode.OnMainWindowClose; MainWindow.Show(); _ = _services.GetRequiredService().InitializeAsync(); } protected override void OnExit(ExitEventArgs e) { _services?.Dispose(); if (_instanceGate is not null) { _instanceGate.ActivationRequested -= OnActivationRequested; _instanceGate.Dispose(); } base.OnExit(e); } internal static void ConfigureServices(IServiceCollection services) { services.AddSingleton(AppVersion.Current); services.AddSingleton(); services.AddSingleton(provider => provider.GetRequiredService().Load()); services.AddSingleton(); services.AddSingleton(provider => provider.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); } // A second launch — another click on "Settings" in the tray menu, say. The gate // answers on a thread pool thread, and a window obeys only its own private void OnActivationRequested(object? sender, EventArgs e) => Dispatcher.BeginInvoke(ShowMainWindow); private void ShowMainWindow() { if (MainWindow is not { } window) { return; } if (window.WindowState == WindowState.Minimized) { window.WindowState = WindowState.Normal; } window.Show(); window.Activate(); } }