namespace LlamaApp { /// /// Provides application-specific behavior to supplement the default Application class. /// public partial class App : Application { private MainWindow? _window; private TrayIconManager? _trayIcon; private OverlayWindow? _overlay; private GlobalHotkey? _hotkey; /// /// Initializes the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// public App() { // Initialize the global logger first, before anything that can // fail: a debug build logs at Debug level, a release build at Info. // The log lives at %LOCALAPPDATA%\LlamaApp\logs\LlamaApp-YYYYMMDD.log // (rolled daily, old files swept to a 7-day retention). Common.Log.Initialize(level: #if DEBUG Common.LogLevel.Debug #else Common.LogLevel.Info #endif ); Common.Log.Info("LlamaApp starting"); _dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread() ?? throw new InvalidOperationException("App must initialize on the UI thread."); InitializeComponent(); } private Microsoft.UI.Dispatching.DispatcherQueue _dispatcher = null!; /// /// Invoked when the end user launches the application normally. Other entry points /// will be used, such as when the application is launched to open a specific file. /// /// Details about the launch request and process. protected override async void OnLaunched(LaunchActivatedEventArgs e) { // Single-instance: a second launch redirects its activation to the // already-running instance (which re-opens the flyout — see the // Activated handler below) and exits, instead of creating a // duplicate tray icon and a second /models poller fighting over // the same server port. var instance = Microsoft.Windows.AppLifecycle.AppInstance .FindOrRegisterForKey("LlamaApp"); if (!instance.IsCurrent) { Common.Log.Info("another instance is already running; redirecting activation and exiting"); try { await instance.RedirectActivationToAsync( instance.GetActivatedEventArgs()); } catch (Exception ex) { Common.Log.Warn(ex, "activation redirect failed"); } Environment.Exit(0); return; } // Create the llama-server manager singleton with the configured // port BEFORE anything else can touch LlamaManager.Shared — the // MainWindow constructor below already subscribes to its events. // The port is baked into the manager at construction, so a value // changed in Settings only takes effect on the next launch. var serverPort = Settings.Current.ServerPort; if (serverPort is < 1 or > 65535) { Common.Log.Warn($"configured server port {serverPort} is out of range; falling back to {Llama.LlamaManager.DefaultServerPort}"); serverPort = Llama.LlamaManager.DefaultServerPort; } Llama.LlamaManager.Initialize(serverPort); // The app is tray-only: the main window is created but never shown // on launch. It is revealed on demand as a flyout anchored to the // system-tray icon (see TrayIconManager / MainWindow.ShowAsFlyout), // mirroring the macOS menu-bar app on Windows 11. _window = new MainWindow(); // The footer's Quit button asks the tray manager to tear the app // down (dispose the icon, close the window, exit). _window.ExitRequested += () => _trayIcon?.RequestExit(); // Add the system-tray icon with its right-click context menu. _trayIcon = new TrayIconManager(_window); // Toast notifications for background events (model loaded, download // failed) while the flyout is hidden. Clicking a toast re-opens the // flyout; so does a redirected second-launch activation. Notifications.Initialize(); Notifications.Invoked += () => _dispatcher.TryEnqueue(() => _trayIcon?.ShowFlyout()); instance.Activated += (_, _) => _dispatcher.TryEnqueue(() => _trayIcon?.ShowFlyout()); // Ensure a llama.cpp server is reachable on the configured port: // adopt an already-running one, launch one from a found binary, or // download the binary via install.ps1 then launch. Fire-and-forget; // once reachable the MainWindow fetches the Available model list via // GET /models. Llama.LlamaManager.Shared.CacheDirectory = Settings.Current.CacheDirectory; Llama.LlamaManager.Shared.HuggingFaceToken = Settings.Current.HuggingFaceToken; Common.Log.Info($"cache directory: {Settings.Current.CacheDirectory}"); // Presence only — never log the token itself. Common.Log.Info($"HF token: {(string.IsNullOrWhiteSpace(Settings.Current.HuggingFaceToken) ? "not set" : "configured")}"); _ = Llama.LlamaManager.Shared.EnsureLlamaOrDownloadAsync(); // Spotlight-style prompt overlay, summoned by a global Alt+Space // hotkey. Created lazily on first press and reused thereafter; the // hotkey is registered for the app's lifetime only, so it stops // working the moment LlamaApp exits. _overlay = new OverlayWindow(); _hotkey = new GlobalHotkey(); _hotkey.Register(() => _dispatcher.TryEnqueue(() => _overlay.Summon())); // The hotkey is hard-coded and otherwise undiscoverable — say so // when it can't work instead of the shortcut silently doing // nothing (RegisterHotKey fails when another app owns Alt+Space). if (!_hotkey.IsRegistered) { Notifications.Show("Alt+Space unavailable", "Another app is using the Alt+Space shortcut, so the chat overlay hotkey is off."); } // One-time first-run hint: the app is tray-only (no window appears // on launch) and the overlay hotkey is undiscoverable — tell the // user both, exactly once. if (!Settings.Current.TrayHintShown) { Settings.Current.TrayHintShown = true; Settings.Current.Save(); Notifications.Show("LlamaApp is running", "Find it in the system tray — and press Alt+Space anytime to chat with a loaded model."); } } } }