lightweight variant (#1)
Reviewed-on: #1 Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using System.ComponentModel;
|
||||
using CursorLang.Agent.Services;
|
||||
using CursorLang.Agent.Windows;
|
||||
using CursorLang.Core.Models;
|
||||
using CursorLang.Core.Services;
|
||||
using CursorLang.Core.Threading;
|
||||
|
||||
namespace CursorLang.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// The background half of CursorLang: the hook, the layout polling, the popup and the
|
||||
/// tray icon, with a Win32 message loop underneath and no WPF anywhere.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The services are wired by hand rather than through a container, and that is a
|
||||
/// decision rather than an omission: the whole point of this process is how little it
|
||||
/// weighs, and a container is a megabyte of assembly and a graph of reflection on the
|
||||
/// way to the same object. There are ten of them and they are all listed here.
|
||||
/// </remarks>
|
||||
internal sealed class Agent : IDisposable
|
||||
{
|
||||
private readonly SingleInstanceGate _gate;
|
||||
private readonly AgentWindow _window;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly AppSettings _settings;
|
||||
private readonly LocalizationService _localization;
|
||||
private readonly NativePopupWindow _popupWindow;
|
||||
private readonly LayoutPopupService _popupService;
|
||||
private readonly KeyboardLayoutService _layoutService;
|
||||
private readonly CapsLockHotkeyService _hotkeyService;
|
||||
private readonly LayoutNotificationCoordinator _notifications;
|
||||
private readonly CapsLockSwitchCoordinator _capsLock;
|
||||
private readonly NativeTrayIcon _tray;
|
||||
|
||||
internal Agent(SingleInstanceGate gate)
|
||||
{
|
||||
_gate = gate;
|
||||
|
||||
_window = new AgentWindow();
|
||||
_window.AddFilter(OnWindowMessage);
|
||||
|
||||
_settingsService = new SettingsService();
|
||||
_settings = _settingsService.Load();
|
||||
|
||||
_localization = new LocalizationService { CurrentLanguage = _settings.Language };
|
||||
_settings.PropertyChanged += OnSettingsChanged;
|
||||
|
||||
_popupWindow = new NativePopupWindow(_settings);
|
||||
_popupService = new LayoutPopupService(_popupWindow, _settings);
|
||||
|
||||
_layoutService = new KeyboardLayoutService(new KeyboardLayoutOptions());
|
||||
_hotkeyService = new CapsLockHotkeyService(_settings, _window.Post);
|
||||
|
||||
_notifications = new LayoutNotificationCoordinator(_layoutService, _popupService);
|
||||
_capsLock = new CapsLockSwitchCoordinator(_hotkeyService, _layoutService, _popupService, _settings);
|
||||
|
||||
_tray = new NativeTrayIcon(_window, _localization);
|
||||
}
|
||||
|
||||
/// <summary>Starts everything and pumps messages until the user asks to quit.</summary>
|
||||
internal int Run(bool automatic)
|
||||
{
|
||||
_gate.ActivationRequested += OnActivationRequested;
|
||||
|
||||
_tray.OpenRequested += OnOpenRequested;
|
||||
_tray.ExitRequested += OnExitRequested;
|
||||
|
||||
bool hasTray = _tray.Install();
|
||||
|
||||
_notifications.Start();
|
||||
_capsLock.Start();
|
||||
|
||||
if (!automatic || !hasTray)
|
||||
{
|
||||
SettingsLauncher.Open();
|
||||
}
|
||||
|
||||
return MessageLoop.Run();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gate.ActivationRequested -= OnActivationRequested;
|
||||
|
||||
_tray.OpenRequested -= OnOpenRequested;
|
||||
_tray.ExitRequested -= OnExitRequested;
|
||||
|
||||
_settings.PropertyChanged -= OnSettingsChanged;
|
||||
|
||||
_capsLock.Dispose();
|
||||
_notifications.Dispose();
|
||||
_hotkeyService.Dispose();
|
||||
_layoutService.Dispose();
|
||||
_popupService.Dispose();
|
||||
_settingsService.Dispose();
|
||||
_tray.Dispose();
|
||||
_window.Dispose();
|
||||
}
|
||||
|
||||
// The settings window has written the file and says so. The write was a single
|
||||
// atomic move, so there is nothing to wait for and nothing half-written to read
|
||||
private bool OnWindowMessage(uint message, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (message != SettingsSignal.Message)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_settingsService.Reload();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(AppSettings.Language))
|
||||
{
|
||||
_localization.CurrentLanguage = _settings.Language;
|
||||
}
|
||||
}
|
||||
|
||||
// A second launch of the agent, from the Start menu for instance. The one already
|
||||
// running answers the way the user expects a second launch to be answered
|
||||
private void OnActivationRequested(object? sender, EventArgs e) =>
|
||||
_window.Post(() => SettingsLauncher.Open());
|
||||
|
||||
private void OnOpenRequested(object? sender, EventArgs e) => SettingsLauncher.Open();
|
||||
|
||||
private void OnExitRequested(object? sender, EventArgs e) => _window.Quit();
|
||||
}
|
||||
Reference in New Issue
Block a user