Files
YKanBan/YKanBan/App.axaml.cs
T
yyc12345 4fc8fdda6f feat(m4): single instance IPC
- LaunchCommand: ShowMain / OpenWorkspace(path), doubles as IPC message
- LaunchArguments: first non-blank arg wins, blank-only args -> ShowMain
- IpcProtocol: strict newline-line codec (show / open+path), tolerant
  of trailing-newline framing only
- SingleInstanceService: per-session named mutex decides primary;
  primary serves a named-pipe loop (decode -> dispatch -> ack) with
  handler failures contained; secondary retries send, exits on ack
- Program/App wiring: secondary forwards command and exits; primary
  activates the main window for forwarded commands (workspace switch
  deferred to the workspace UI milestone)
- 15 new tests (protocol codec, argument parsing); smoke-verified:
  instance 1 stays alive, instance 2 self-exits with code 0
2026-10-02 14:29:42 +08:00

67 lines
2.0 KiB
C#

using System.Linq;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using YKanBan.Ipc;
using YKanBan.ViewModels;
using YKanBan.Views;
namespace YKanBan;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
DisableAvaloniaDataAnnotationValidation();
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
// Primary instance: serve launch commands forwarded by secondary instances.
Program.SingleInstance?.StartServer(command => HandleLaunchCommand(desktop, command));
}
base.OnFrameworkInitializationCompleted();
}
/// <summary>
/// Brings the main window to front for a forwarded launch command. Both
/// ShowMain and OpenWorkspace activate the window; switching to the
/// requested workspace lands together with the workspace UI.
/// </summary>
private static void HandleLaunchCommand(IClassicDesktopStyleApplicationLifetime desktop, LaunchCommand command)
{
// IPC delivery happens on a background thread; window access must hop to the UI thread.
Dispatcher.UIThread.Post(() =>
{
if (desktop.MainWindow is { } window)
{
window.Show();
window.Activate();
}
});
}
private static void DisableAvaloniaDataAnnotationValidation()
{
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
}
}