XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEToolBox

【WPF】XFE工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Windows;
using XFEToolBox.Client.Profiles.CrossVersionProfiles;
using XFEToolBox.Client.Utilities;
using XFEToolBox.Client.Utilities.Chat;
using XFEToolBox.Client.Utilities.Server;
using XFEToolBox.Client.Views.Windows;

namespace XFEToolBox.Client;

public partial class App : Application
{
    private SingleInstanceService? singleInstanceService;
    private TrayIconService? trayIconService;
    private GlobalHotkeyService? globalHotkeyService;
    private CommandPaletteWindow? commandPaletteWindow;
    private MainWindow? mainWindow;
    private bool chatRuntimeInitialized;
    private bool chatRuntimeEventsDetached;
    private Task? chatRuntimeShutdownTask;
    private ChatDesktopNotificationCoordinator? chatNotificationCoordinator;
    private readonly SemaphoreSlim chatSessionGate = new(1, 1);

    public App() => InitializeComponent();

    public bool IsExiting { get; private set; }

    public string GlobalHotkeyStatus => globalHotkeyService?.Status ?? "快捷键服务尚未初始化";

    public event EventHandler? GlobalHotkeyStatusChanged;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DispatcherUnhandledException += App_DispatcherUnhandledException;
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

        singleInstanceService = new SingleInstanceService();
        if (!singleInstanceService.IsFirstInstance)
        {
            var command = e.Args.Contains("--palette", StringComparer.OrdinalIgnoreCase) ? "show-palette" : "show-main";
            _ = SingleInstanceService.SendAsync(command).GetAwaiter().GetResult();
            Shutdown();
            return;
        }

        ShutdownMode = ShutdownMode.OnExplicitShutdown;
        InitializeChatRuntime();
        mainWindow = new MainWindow();
        MainWindow = mainWindow;
        globalHotkeyService = new GlobalHotkeyService(mainWindow, () => ShowCommandPalette());
        globalHotkeyService.StatusChanged += (_, _) => GlobalHotkeyStatusChanged?.Invoke(this, EventArgs.Empty);
        if (SystemProfile.LauncherHotkeyEnabled)
            globalHotkeyService.Register(SystemProfile.LauncherHotkey);
        else
            globalHotkeyService.Disable();
        trayIconService = new TrayIconService(
            () => ShowMainWindow("home"),
            () => ShowCommandPalette(),
            RequestExit);
        chatNotificationCoordinator = new ChatDesktopNotificationCoordinator();
        singleInstanceService.StartListening(message => Dispatcher.BeginInvoke(() =>
        {
            if (message.Equals("show-palette", StringComparison.OrdinalIgnoreCase)) ShowCommandPalette();
            else ShowMainWindow("home");
        }));

        if (!e.Args.Contains("--background", StringComparer.OrdinalIgnoreCase))
            ShowMainWindow("home");
    }

    public void ShowMainWindow(string? pageTag = null)
    {
        if (mainWindow is null) return;
        if (!mainWindow.IsVisible) mainWindow.Show();
        if (mainWindow.WindowState == WindowState.Minimized) mainWindow.WindowState = WindowState.Normal;
        if (!string.IsNullOrWhiteSpace(pageTag))
            mainWindow.NavigateAndSelect(pageTag);
        mainWindow.Activate();
        mainWindow.Topmost = true;
        mainWindow.Topmost = false;
        mainWindow.Focus();
    }

    public void ShowCommandPalette(string? initialQuery = null)
    {
        commandPaletteWindow ??= new CommandPaletteWindow();
        commandPaletteWindow.ShowPalette(initialQuery);
    }

    public void ShowDesktopNotification(string title, string message, DesktopNotificationLevel level = DesktopNotificationLevel.Information) =>
        trayIconService?.ShowNotification(title, message, level);

    public bool ConfigureGlobalHotkey(string gestureText)
    {
        if (!GlobalHotkeyService.TryNormalize(gestureText, out var normalized, out _)) return false;
        SystemProfile.LauncherHotkey = normalized;
        SystemProfile.SaveProfile();
        if (!SystemProfile.LauncherHotkeyEnabled)
        {
            globalHotkeyService?.Disable();
            return true;
        }
        return globalHotkeyService?.Register(normalized) == true;
    }

    public void ConfigureGlobalHotkeyEnabled(bool enabled)
    {
        SystemProfile.LauncherHotkeyEnabled = enabled;
        SystemProfile.SaveProfile();
        if (enabled)
            globalHotkeyService?.Register(SystemProfile.LauncherHotkey);
        else
            globalHotkeyService?.Disable();
    }

    public void HideMainWindowToTray()
    {
        mainWindow?.Hide();
        if (SystemProfile.TrayCloseHintShown) return;
        SystemProfile.TrayCloseHintShown = true;
        SystemProfile.SaveProfile();
        trayIconService?.ShowCloseHint();
    }

    public async void RequestExit()
    {
        if (IsExiting) return;
        if (ActivityCenterService.ActiveCount > 0)
        {
            var result = PopupHelper.ShowConfirmDialog(
                $"当前还有以下未完成任务:\n\n{ActivityCenterService.DescribeActiveItems()}\n\n确定退出并终止这些任务吗?",
                showCancelButton: true,
                confirmText: "仍然退出");
            if (result != MessageBoxResult.OK) return;
        }

        IsExiting = true;
        try
        {
            await ShutdownChatRuntimeAsync();
        }
        finally
        {
            Shutdown();
        }
    }

    protected override void OnExit(ExitEventArgs e)
    {
        if (chatRuntimeInitialized)
        {
            DetachChatRuntimeEvents();
            ChatRealtimeClient.Shared.RequestStop();
        }
        globalHotkeyService?.Dispose();
        chatNotificationCoordinator?.Dispose();
        trayIconService?.Dispose();
        singleInstanceService?.Dispose();
        base.OnExit(e);
    }

    private void InitializeChatRuntime()
    {
        if (chatRuntimeInitialized) return;
        chatRuntimeInitialized = true;
        ClientSession.SessionChanged += ClientSession_SessionChanged;
        VoiceCallService.Current.IncomingCallReceived += VoiceCallService_IncomingCallReceived;
        VoiceCallService.Current.Error += VoiceCallService_Error;
    }

    private Task ShutdownChatRuntimeAsync()
    {
        if (!chatRuntimeInitialized) return Task.CompletedTask;
        return chatRuntimeShutdownTask ??= ShutdownChatRuntimeCoreAsync();
    }

    private async Task ShutdownChatRuntimeCoreAsync()
    {
        DetachChatRuntimeEvents();
        await chatSessionGate.WaitAsync();
        try
        {
            try
            {
                await VoiceCallService.Current.DisposeAsync();
            }
            catch
            {
            }

            try
            {
                await ChatRealtimeClient.Shared.StopAsync().WaitAsync(TimeSpan.FromSeconds(3));
            }
            catch
            {
                ChatRealtimeClient.Shared.RequestStop();
            }
        }
        finally
        {
            chatSessionGate.Release();
        }
    }

    private void DetachChatRuntimeEvents()
    {
        if (chatRuntimeEventsDetached) return;
        chatRuntimeEventsDetached = true;
        ClientSession.SessionChanged -= ClientSession_SessionChanged;
        VoiceCallService.Current.IncomingCallReceived -= VoiceCallService_IncomingCallReceived;
        VoiceCallService.Current.Error -= VoiceCallService_Error;
    }

    private async void ClientSession_SessionChanged(object? sender, EventArgs e)
    {
        if (!Dispatcher.CheckAccess())
        {
            var operation = Dispatcher.InvokeAsync(UpdateChatRealtimeSessionAsync);
            await await operation;
            return;
        }
        await UpdateChatRealtimeSessionAsync();
    }

    private async Task UpdateChatRealtimeSessionAsync()
    {
        await chatSessionGate.WaitAsync();
        try
        {
            if (IsExiting) return;
            if (ClientSession.IsLoggedIn)
            {
                await VoiceCallService.Current.StartAsync();
                return;
            }

            try { await VoiceCallService.Current.LeaveAsync(); }
            catch { }
            await ChatRealtimeClient.Shared.StopAsync();
        }
        finally
        {
            chatSessionGate.Release();
        }
    }

    private async void VoiceCallService_IncomingCallReceived(object? sender, IncomingVoiceCallEventArgs e)
    {
        if (!ClientSession.IsLoggedIn) return;
        var invitation = e.Invitation;
        var result = PopupHelper.ShowYesOrNoDialog(
            $"{invitation.FromDisplayName} 邀请你加入语音通话。",
            showCancelButton: false,
            yesText: "接听",
            noText: "拒绝");
        try
        {
            if (result == MessageBoxResult.Yes)
                await VoiceCallService.Current.AcceptAsync(invitation.CallId);
            else
                await VoiceCallService.Current.RejectAsync(invitation.CallId);
        }
        catch (Exception exception)
        {
            PopupHelper.ShowConfirmDialog($"无法处理通话邀请:{exception.Message}", confirmText: "知道了");
        }
    }

    private void VoiceCallService_Error(object? sender, VoiceCallErrorEventArgs e)
    {
        if (!IsExiting)
            PopupHelper.ShowConfirmDialog(e.Message, confirmText: "知道了");
    }

    private static void App_DispatcherUnhandledException(
        object sender,
        System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
    {
        e.Handled = true;
    }

    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
    }

    private static void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) =>
        e.SetObserved();
}