2026-04-05 15:04:37 +08:00
|
|
|
|
using H.NotifyIcon;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
using Ink_Canvas.Helpers;
|
2026-04-10 01:24:57 +08:00
|
|
|
|
using Ink_Canvas.Plugins;
|
2026-03-21 16:13:42 +08:00
|
|
|
|
using Ink_Canvas.Properties;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
using iNKORE.UI.WPF.Modern.Controls;
|
|
|
|
|
|
using Microsoft.Win32;
|
|
|
|
|
|
using Newtonsoft.Json;
|
2026-03-21 16:13:42 +08:00
|
|
|
|
using Sentry;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
using System.Diagnostics;
|
2026-04-18 22:32:08 +08:00
|
|
|
|
using System.Globalization;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
using System.IO;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Linq;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Net;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Reflection;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
using System.Threading;
|
2025-10-01 18:17:39 +08:00
|
|
|
|
using System.Threading.Tasks;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Windows;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
using System.Windows.Input;
|
2026-04-30 14:50:57 +08:00
|
|
|
|
using System.Windows.Interop;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Windows.Threading;
|
|
|
|
|
|
using Application = System.Windows.Application;
|
2025-09-13 21:41:34 +08:00
|
|
|
|
using MessageBox = System.Windows.MessageBox;
|
2025-10-01 18:17:39 +08:00
|
|
|
|
using SplashScreen = Ink_Canvas.Windows.SplashScreen;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using Timer = System.Threading.Timer;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Ink_Canvas
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Interaction logic for App.xaml
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
Mutex mutex;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2026-04-18 22:32:08 +08:00
|
|
|
|
public void ReleaseMutexForRestart()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mutex != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
mutex.ReleaseMutex();
|
|
|
|
|
|
mutex.Dispose();
|
|
|
|
|
|
mutex = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
public static string[] StartArgs;
|
2025-11-29 16:46:33 +08:00
|
|
|
|
public static string RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-13 14:59:47 +08:00
|
|
|
|
// 新增:标记是否通过--board参数启动
|
2025-10-01 00:53:22 +08:00
|
|
|
|
public static bool StartWithBoardMode = false;
|
2025-10-02 15:30:51 +08:00
|
|
|
|
// 新增:标记是否通过--show参数启动
|
|
|
|
|
|
public static bool StartWithShowMode = false;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
// 新增:保存看门狗进程对象
|
2025-10-25 19:57:56 +08:00
|
|
|
|
public static Process watchdogProcess;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
// 新增:标记是否为软件内主动退出
|
2025-07-28 14:40:44 +08:00
|
|
|
|
public static bool IsAppExitByUser;
|
2026-02-16 20:22:41 +08:00
|
|
|
|
// 新增:标记是否正在触发安装更新(用于跳过某些交互确认)
|
|
|
|
|
|
public static bool IsUpdateInstalling;
|
2025-10-18 18:26:13 +08:00
|
|
|
|
// 新增:标记是否启用了UIA置顶功能
|
|
|
|
|
|
public static bool IsUIAccessTopMostEnabled;
|
2026-02-14 15:59:10 +08:00
|
|
|
|
// 新增:标记是否正在显示 OOBE(首次启动向导),看门狗在此期间不判定为卡死/假死
|
|
|
|
|
|
public static bool IsOobeShowing;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
// 新增:退出信号文件路径
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private static string watchdogExitSignalFile = Path.Combine(Path.GetTempPath(), "icc_watchdog_exit_" + Process.GetCurrentProcess().Id + ".flag");
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 新增:崩溃日志文件路径
|
2025-11-29 16:46:33 +08:00
|
|
|
|
private static string crashLogFile = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Crashes");
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 新增:进程ID
|
|
|
|
|
|
private static int currentProcessId = Process.GetCurrentProcess().Id;
|
|
|
|
|
|
// 新增:应用启动时间
|
2025-08-18 18:01:23 +08:00
|
|
|
|
internal static DateTime appStartTime { get; private set; }
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 新增:最后一次错误信息
|
|
|
|
|
|
private static string lastErrorMessage = string.Empty;
|
|
|
|
|
|
// 新增:是否已初始化崩溃监听器
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private static bool crashListenersInitialized;
|
2026-04-30 14:50:57 +08:00
|
|
|
|
private IntPtr processDestroyHook = IntPtr.Zero;
|
|
|
|
|
|
private IntPtr monitoredMainWindowHandle = IntPtr.Zero;
|
|
|
|
|
|
private bool mainWindowDestroyedLogged;
|
|
|
|
|
|
private WinEventDelegate processDestroyHookCallback;
|
2025-10-01 18:17:39 +08:00
|
|
|
|
// 新增:启动画面相关
|
|
|
|
|
|
private static SplashScreen _splashScreen;
|
|
|
|
|
|
private static bool _isSplashScreenShown = false;
|
2026-04-30 15:04:54 +08:00
|
|
|
|
private static System.Resources.ResourceSet _pendingLocalizedResourceSet;
|
2026-04-30 15:26:47 +08:00
|
|
|
|
private static readonly Stopwatch startupStopwatch = new Stopwatch();
|
|
|
|
|
|
private static readonly Stopwatch splashStopwatch = new Stopwatch();
|
2025-06-12 10:34:29 +08:00
|
|
|
|
|
2025-12-20 19:16:39 +08:00
|
|
|
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
|
|
|
|
private static extern int SetCurrentProcessExplicitAppUserModelID(string appId);
|
|
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
public App()
|
|
|
|
|
|
{
|
2025-12-20 19:16:39 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
SetCurrentProcessExplicitAppUserModelID("InkCanvasForClass.CE");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 22:47:41 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var dsn = GetDlassTelemetryDsn();
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(dsn))
|
|
|
|
|
|
{
|
|
|
|
|
|
SentrySdk.Init(options =>
|
|
|
|
|
|
{
|
|
|
|
|
|
options.Dsn = dsn;
|
|
|
|
|
|
options.Debug = false;
|
|
|
|
|
|
options.SendDefaultPii = true;
|
|
|
|
|
|
options.TracesSampleRate = 1.0;
|
|
|
|
|
|
options.IsGlobalModeEnabled = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"初始化 Dlass 遥测失败: {ex}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 配置TLS协议以支持Windows 7
|
|
|
|
|
|
ConfigureTlsForWindows7();
|
|
|
|
|
|
|
2025-07-06 15:39:37 +08:00
|
|
|
|
// 如果是看门狗子进程,直接进入看门狗主循环并终止主流程
|
|
|
|
|
|
var args = Environment.GetCommandLineArgs();
|
|
|
|
|
|
if (args.Length >= 2 && args[1] == "--watchdog")
|
|
|
|
|
|
{
|
|
|
|
|
|
RunWatchdogIfNeeded();
|
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动时优先同步设置,确保CrashAction为最新
|
|
|
|
|
|
SyncCrashActionFromSettings();
|
|
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
Startup += App_Startup;
|
|
|
|
|
|
DispatcherUnhandledException += App_DispatcherUnhandledException;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
StartHeartbeatMonitor();
|
2025-07-06 15:39:37 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 初始化全局异常和进程结束处理
|
2025-07-15 16:06:29 +08:00
|
|
|
|
InitializeCrashListeners();
|
|
|
|
|
|
|
2025-07-06 15:39:37 +08:00
|
|
|
|
// 仅在崩溃后操作为静默重启时才启动看门狗
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 在更新模式下不启动看门狗,避免干扰更新流程
|
|
|
|
|
|
args = Environment.GetCommandLineArgs();
|
|
|
|
|
|
bool isUpdateMode = args.Contains("--update-mode");
|
|
|
|
|
|
bool isFinalApp = args.Contains("--final-app");
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
if (CrashAction == CrashActionType.SilentRestart && !isUpdateMode && !isFinalApp)
|
2025-07-06 15:39:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
StartWatchdogIfNeeded();
|
|
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
Exit += App_Exit; // 注册退出事件
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 配置TLS协议以支持Windows 7
|
2025-07-19 16:51:03 +08:00
|
|
|
|
private void ConfigureTlsForWindows7()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检测操作系统版本
|
|
|
|
|
|
var osVersion = Environment.OSVersion;
|
|
|
|
|
|
bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (isWindows7)
|
|
|
|
|
|
{
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 启用所有TLS版本以支持Windows 7
|
|
|
|
|
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 配置ServicePointManager以支持Windows 7
|
|
|
|
|
|
ServicePointManager.DefaultConnectionLimit = 10;
|
|
|
|
|
|
ServicePointManager.Expect100Continue = false;
|
|
|
|
|
|
ServicePointManager.UseNagleAlgorithm = false;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 对于更新的Windows版本,不进行任何TLS配置,使用系统默认设置
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-29 16:46:33 +08:00
|
|
|
|
catch (Exception)
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 初始化崩溃监听器
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private void InitializeCrashListeners()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (crashListenersInitialized) return;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 确保崩溃日志目录存在
|
|
|
|
|
|
if (!Directory.Exists(crashLogFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(crashLogFile);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 注册非UI线程未处理异常处理程序
|
|
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 注册控制台Ctrl+C等终止信号处理
|
|
|
|
|
|
Console.CancelKeyPress += Console_CancelKeyPress;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 注册系统会话结束事件(关机、注销等)
|
|
|
|
|
|
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 注册进程退出处理程序
|
|
|
|
|
|
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 尝试注册Windows关闭消息监听
|
|
|
|
|
|
SetConsoleCtrlHandler(ConsoleCtrlHandler, true);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
TrySetupTerminationMonitoring();
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
2026-04-30 14:50:57 +08:00
|
|
|
|
catch (Exception monitorEx)
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"设置终止监控失败: {monitorEx.Message}", LogHelper.LogType.Warning);
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
crashListenersInitialized = true;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("已初始化崩溃监听器");
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"初始化崩溃监听器失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
private void TrySetupTerminationMonitoring()
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
processDestroyHookCallback = OnWinEventMainWindowDestroyed;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
// 等主窗口句柄可用后再开始监听
|
|
|
|
|
|
Dispatcher.BeginInvoke(new Action(BindMainWindowLifecycle), DispatcherPriority.ApplicationIdle);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"初始化终止监控失败: {ex.GetType().FullName}: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
private void BindMainWindowLifecycle()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Current?.MainWindow == null)
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
Current.MainWindow.SourceInitialized -= MainWindow_SourceInitialized;
|
|
|
|
|
|
Current.MainWindow.SourceInitialized += MainWindow_SourceInitialized;
|
|
|
|
|
|
}
|
2026-05-01 00:27:29 +08:00
|
|
|
|
catch (Exception)
|
2026-04-30 14:50:57 +08:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
private void MainWindow_SourceInitialized(object sender, EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(sender is Window window))
|
2026-04-30 14:44:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
monitoredMainWindowHandle = new WindowInteropHelper(window).Handle;
|
|
|
|
|
|
if (monitoredMainWindowHandle == IntPtr.Zero)
|
2026-04-30 14:44:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
RegisterMainWindowDestroyHook();
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
2026-05-01 00:07:18 +08:00
|
|
|
|
catch (Exception)
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-30 14:50:57 +08:00
|
|
|
|
private void RegisterMainWindowDestroyHook()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (processDestroyHook != IntPtr.Zero || monitoredMainWindowHandle == IntPtr.Zero)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
processDestroyHook = SetWinEventHook(
|
|
|
|
|
|
EVENT_OBJECT_DESTROY,
|
|
|
|
|
|
EVENT_OBJECT_DESTROY,
|
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
|
processDestroyHookCallback,
|
|
|
|
|
|
(uint)currentProcessId,
|
|
|
|
|
|
0,
|
|
|
|
|
|
WINEVENT_OUTOFCONTEXT);
|
|
|
|
|
|
|
|
|
|
|
|
if (processDestroyHook == IntPtr.Zero)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnWinEventMainWindowDestroyed(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (eventType != EVENT_OBJECT_DESTROY || mainWindowDestroyedLogged)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (idObject != OBJID_WINDOW || idChild != CHILDID_SELF)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (hwnd != monitoredMainWindowHandle || hwnd == IntPtr.Zero)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mainWindowDestroyedLogged = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CleanupTerminationMonitoring()
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
if (processDestroyHook != IntPtr.Zero)
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
UnhookWinEvent(processDestroyHook);
|
|
|
|
|
|
processDestroyHook = IntPtr.Zero;
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-30 14:50:57 +08:00
|
|
|
|
catch
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// Windows控制台控制处理程序
|
2025-07-15 16:06:29 +08:00
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
|
|
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private delegate bool ConsoleCtrlDelegate(int ctrlType);
|
2026-04-30 14:50:57 +08:00
|
|
|
|
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
|
|
|
|
|
|
|
|
|
|
|
|
private const uint EVENT_OBJECT_DESTROY = 0x8001;
|
|
|
|
|
|
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
|
|
|
|
|
private const int OBJID_WINDOW = 0;
|
|
|
|
|
|
private const int CHILDID_SELF = 0;
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll")]
|
|
|
|
|
|
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll")]
|
|
|
|
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
|
|
|
|
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private static bool ConsoleCtrlHandler(int ctrlType)
|
|
|
|
|
|
{
|
|
|
|
|
|
string eventType = "未知控制类型";
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 使用传统switch语句替代switch表达式
|
|
|
|
|
|
switch (ctrlType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
eventType = "CTRL_C_EVENT";
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
eventType = "CTRL_BREAK_EVENT";
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
eventType = "CTRL_CLOSE_EVENT";
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 5:
|
|
|
|
|
|
eventType = "CTRL_LOGOFF_EVENT";
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 6:
|
|
|
|
|
|
eventType = "CTRL_SHUTDOWN_EVENT";
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
eventType = $"未知控制类型({ctrlType})";
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
WriteCrashLog($"接收到系统控制信号: {eventType}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 返回true表示已处理该事件
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 系统会话结束事件处理
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机";
|
|
|
|
|
|
WriteCrashLog($"系统会话即将结束: {reason}");
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-10-02 11:50:00 +08:00
|
|
|
|
// 清理PowerPoint进程守护和悬浮窗拦截器
|
2025-09-13 10:48:33 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-10-02 11:50:00 +08:00
|
|
|
|
// 获取主窗口实例
|
2025-09-30 19:15:03 +08:00
|
|
|
|
var mainWindow = Current.MainWindow as MainWindow;
|
|
|
|
|
|
if (mainWindow != null)
|
2025-09-13 10:48:33 +08:00
|
|
|
|
{
|
2025-10-02 11:50:00 +08:00
|
|
|
|
// 清理PowerPoint进程守护
|
2025-10-03 17:08:46 +08:00
|
|
|
|
var method = mainWindow.GetType().GetMethod("StopPowerPointProcessMonitoring",
|
2025-09-13 21:38:03 +08:00
|
|
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
2025-09-13 10:48:33 +08:00
|
|
|
|
method?.Invoke(mainWindow, null);
|
|
|
|
|
|
WriteCrashLog("PowerPoint进程守护已在系统关机时清理");
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-10-02 11:50:00 +08:00
|
|
|
|
// 清理悬浮窗拦截器
|
2025-10-03 17:08:46 +08:00
|
|
|
|
var interceptorField = mainWindow.GetType().GetField("_floatingWindowInterceptorManager",
|
2025-10-02 11:50:00 +08:00
|
|
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
|
|
var interceptorManager = interceptorField?.GetValue(mainWindow);
|
|
|
|
|
|
if (interceptorManager != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var disposeMethod = interceptorManager.GetType().GetMethod("Dispose");
|
|
|
|
|
|
disposeMethod?.Invoke(interceptorManager, null);
|
|
|
|
|
|
}
|
2025-09-13 10:48:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-10-02 11:50:00 +08:00
|
|
|
|
WriteCrashLog($"清理资源失败: {ex.Message}");
|
2025-09-13 10:48:33 +08:00
|
|
|
|
}
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-08-18 17:57:56 +08:00
|
|
|
|
DeviceIdentifier.SaveUsageStatsOnShutdown();
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 控制台取消事件处理
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteCrashLog($"接收到控制台中断信号: {e.SpecialKey}");
|
|
|
|
|
|
e.Cancel = true; // 取消默认处理
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 处理非UI线程的未处理异常
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var exception = e.ExceptionObject as Exception;
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2025-11-15 19:31:57 +08:00
|
|
|
|
if (exception is InvalidOperationException invalidOpEx)
|
|
|
|
|
|
{
|
|
|
|
|
|
string exceptionMessage = invalidOpEx.Message ?? "";
|
|
|
|
|
|
string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
|
|
|
|
|
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
2025-11-15 19:31:57 +08:00
|
|
|
|
exceptionMessage.Contains("because another thread owns it") ||
|
|
|
|
|
|
exceptionStackTrace.Contains("DynamicRenderer") ||
|
|
|
|
|
|
exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile(
|
|
|
|
|
|
$"检测到DynamicRenderer线程访问异常: {invalidOpEx.Message}",
|
|
|
|
|
|
LogHelper.LogType.Warning
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
string errorMessage = exception?.ToString() ?? "未知异常";
|
|
|
|
|
|
lastErrorMessage = errorMessage;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
WriteCrashLog($"捕获到未处理的异常: {errorMessage}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
if (e.IsTerminating)
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteCrashLog("应用程序即将终止");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尝试在最后时刻记录错误
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-12-20 13:56:46 +08:00
|
|
|
|
string timeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
|
|
|
|
|
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
2025-11-29 16:46:33 +08:00
|
|
|
|
: DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
2025-07-15 16:06:29 +08:00
|
|
|
|
File.AppendAllText(
|
2025-11-29 16:46:33 +08:00
|
|
|
|
Path.Combine(crashLogFile, $"Crash_{timeStr}.txt"),
|
2025-07-15 16:06:29 +08:00
|
|
|
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 记录未处理异常时发生错误: {ex.Message}\r\n"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 处理进程退出事件
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
|
|
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
CleanupTerminationMonitoring();
|
2025-07-15 16:06:29 +08:00
|
|
|
|
TimeSpan runDuration = DateTime.Now - appStartTime;
|
2025-08-24 01:46:48 +08:00
|
|
|
|
string durationText = FormatTimeSpan(runDuration);
|
|
|
|
|
|
WriteCrashLog($"应用程序退出,运行时长: {durationText}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 如果有最后错误消息,记录到日志
|
|
|
|
|
|
if (!string.IsNullOrEmpty(lastErrorMessage))
|
|
|
|
|
|
{
|
|
|
|
|
|
WriteCrashLog($"最后错误信息: {lastErrorMessage}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 格式化时间跨度
|
2025-08-24 01:46:48 +08:00
|
|
|
|
private static string FormatTimeSpan(TimeSpan timeSpan)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (timeSpan.TotalDays >= 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
return $"{timeSpan.Days}天 {timeSpan.Hours}小时 {timeSpan.Minutes}分钟";
|
|
|
|
|
|
}
|
2025-08-31 09:54:13 +08:00
|
|
|
|
|
|
|
|
|
|
if (timeSpan.TotalHours >= 1)
|
2025-08-24 01:46:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
return $"{timeSpan.Hours}小时 {timeSpan.Minutes}分钟";
|
|
|
|
|
|
}
|
2025-08-31 09:54:13 +08:00
|
|
|
|
|
|
|
|
|
|
if (timeSpan.TotalMinutes >= 1)
|
2025-08-24 01:46:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
return $"{timeSpan.Minutes}分钟 {timeSpan.Seconds}秒";
|
|
|
|
|
|
}
|
2025-08-31 09:54:13 +08:00
|
|
|
|
|
|
|
|
|
|
return $"{timeSpan.Seconds}秒";
|
2025-08-24 01:46:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:17:39 +08:00
|
|
|
|
public static void ShowSplashScreen()
|
|
|
|
|
|
{
|
2025-10-03 17:08:46 +08:00
|
|
|
|
if (_isSplashScreenShown)
|
2025-10-01 18:17:39 +08:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("启动画面已经显示,跳过重复显示");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-10-01 18:17:39 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("开始创建启动画面...");
|
|
|
|
|
|
_splashScreen = new SplashScreen();
|
|
|
|
|
|
LogHelper.WriteLogToFile("启动画面对象创建成功,准备显示...");
|
2026-01-17 16:32:57 +08:00
|
|
|
|
_splashScreen.Show();
|
|
|
|
|
|
_isSplashScreenShown = true;
|
2026-03-07 12:13:29 +08:00
|
|
|
|
splashScreenStartTime = DateTime.Now;
|
2026-04-30 15:26:47 +08:00
|
|
|
|
splashStopwatch.Restart();
|
2026-01-17 16:32:57 +08:00
|
|
|
|
LogHelper.WriteLogToFile("启动画面已显示");
|
2025-10-01 18:17:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"显示启动画面失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"异常堆栈: {ex.StackTrace}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
// 关闭启动画面
|
2025-10-01 18:17:39 +08:00
|
|
|
|
public static void CloseSplashScreen()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isSplashScreenShown || _splashScreen == null) return;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-10-01 18:17:39 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_splashScreen.CloseSplashScreen();
|
|
|
|
|
|
_isSplashScreenShown = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"关闭启动画面失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
// 设置启动画面进度
|
2025-10-01 18:17:39 +08:00
|
|
|
|
public static void SetSplashProgress(int progress)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splashScreen != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_splashScreen.SetProgress(progress);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
// 设置启动画面消息
|
2025-10-01 18:17:39 +08:00
|
|
|
|
public static void SetSplashMessage(string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splashScreen != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_splashScreen.SetLoadingMessage(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
private static bool ShouldShowSplashScreen()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查设置文件中的启动动画开关
|
|
|
|
|
|
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
|
|
|
|
|
|
if (File.Exists(settingsPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
var json = File.ReadAllText(settingsPath);
|
|
|
|
|
|
dynamic obj = JsonConvert.DeserializeObject(json);
|
|
|
|
|
|
if (obj?["appearance"]?["enableSplashScreen"] != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (bool)obj["appearance"]["enableSplashScreen"];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
// 如果设置文件不存在或没有该设置,返回默认值false
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"检查启动动画设置失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 20:52:14 +08:00
|
|
|
|
private static bool IsLaunchByFileOrUri(string[] args)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (args == null || args.Length == 0) return false;
|
|
|
|
|
|
foreach (string a in args)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(a)) continue;
|
|
|
|
|
|
string t = a.Trim();
|
|
|
|
|
|
if (t.StartsWith("icc:", StringComparison.OrdinalIgnoreCase)) return true;
|
|
|
|
|
|
if (Path.GetExtension(t).Equals(".icstk", StringComparison.OrdinalIgnoreCase)) return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
// 记录崩溃日志
|
2025-07-15 16:06:29 +08:00
|
|
|
|
private static void WriteCrashLog(string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 确保目录存在
|
|
|
|
|
|
if (!Directory.Exists(crashLogFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(crashLogFile);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-12-20 13:56:46 +08:00
|
|
|
|
string appStartTimeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
|
|
|
|
|
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
2025-11-29 16:46:33 +08:00
|
|
|
|
: DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
|
|
|
|
|
string logFileName = Path.Combine(crashLogFile, $"Crash_{appStartTimeStr}.txt");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 收集系统状态信息
|
2025-07-28 14:40:44 +08:00
|
|
|
|
string memoryUsage = (Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024)) + " MB";
|
2025-07-15 16:06:29 +08:00
|
|
|
|
string cpuTime = Process.GetCurrentProcess().TotalProcessorTime.ToString();
|
2025-08-24 01:46:48 +08:00
|
|
|
|
string processUptime = FormatTimeSpan(DateTime.Now - Process.GetCurrentProcess().StartTime);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
string statusInfo = $"[内存: {memoryUsage}, CPU时间: {cpuTime}, 运行时长: {processUptime}]";
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 写入日志
|
|
|
|
|
|
File.AppendAllText(
|
|
|
|
|
|
logFileName,
|
|
|
|
|
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [PID:{currentProcessId}] {message}\r\n{statusInfo}\r\n\r\n"
|
|
|
|
|
|
);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-15 16:06:29 +08:00
|
|
|
|
// 同时记录到主日志
|
|
|
|
|
|
LogHelper.WriteLogToFile(message, LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 10:13:47 +08:00
|
|
|
|
// 增加字段保存崩溃后操作设置
|
|
|
|
|
|
public static CrashActionType CrashAction = CrashActionType.SilentRestart;
|
|
|
|
|
|
|
2025-07-06 15:39:37 +08:00
|
|
|
|
public static void SyncCrashActionFromSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 优先从 Settings.json 直接读取
|
2025-09-13 21:10:36 +08:00
|
|
|
|
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
|
2025-07-06 15:39:37 +08:00
|
|
|
|
if (File.Exists(settingsPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
var json = File.ReadAllText(settingsPath);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
dynamic obj = JsonConvert.DeserializeObject(json);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
int crashAction = 0;
|
2026-02-21 16:51:34 +08:00
|
|
|
|
try { crashAction = (int)(obj["startup"]["crashAction"] ?? 0); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-07-06 15:39:37 +08:00
|
|
|
|
CrashAction = (CrashActionType)crashAction;
|
|
|
|
|
|
}
|
2025-10-02 00:05:09 +08:00
|
|
|
|
// 从主窗口同步
|
2025-07-06 15:39:37 +08:00
|
|
|
|
else if (Ink_Canvas.MainWindow.Settings != null && Ink_Canvas.MainWindow.Settings.Startup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
CrashAction = (CrashActionType)Ink_Canvas.MainWindow.Settings.Startup.CrashAction;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-07-06 15:39:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-11-15 19:31:57 +08:00
|
|
|
|
// 检查是否是DynamicRenderer线程访问UI对象的已知问题
|
|
|
|
|
|
if (e.Exception is InvalidOperationException invalidOpEx)
|
|
|
|
|
|
{
|
|
|
|
|
|
string exceptionMessage = invalidOpEx.Message ?? "";
|
|
|
|
|
|
string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2025-11-15 19:31:57 +08:00
|
|
|
|
// 检查是否是DynamicRenderer相关的线程访问问题
|
2025-12-20 13:56:46 +08:00
|
|
|
|
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
2025-11-15 19:31:57 +08:00
|
|
|
|
exceptionMessage.Contains("because another thread owns it") ||
|
|
|
|
|
|
exceptionStackTrace.Contains("DynamicRenderer") ||
|
|
|
|
|
|
exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 这是WPF InkCanvas的已知问题,DynamicRenderer的后台线程尝试访问UI对象
|
|
|
|
|
|
// 这个异常不会影响应用程序功能,可以安全地忽略
|
|
|
|
|
|
LogHelper.WriteLogToFile(
|
|
|
|
|
|
$"检测到DynamicRenderer线程访问异常(已安全处理): {invalidOpEx.Message}",
|
|
|
|
|
|
LogHelper.LogType.Warning
|
|
|
|
|
|
);
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2025-11-15 19:31:57 +08:00
|
|
|
|
// 标记为已处理,不显示错误消息,不触发重启
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2026-02-23 14:31:48 +08:00
|
|
|
|
Ink_Canvas.MainWindow.ShowNewMessage(Strings.GetString("Msg_UnexpectedError"));
|
2025-05-25 09:29:48 +08:00
|
|
|
|
LogHelper.NewLog(e.Exception.ToString());
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-10-02 00:05:09 +08:00
|
|
|
|
// 记录到崩溃日志
|
2025-07-15 16:06:29 +08:00
|
|
|
|
lastErrorMessage = e.Exception.ToString();
|
|
|
|
|
|
WriteCrashLog($"UI线程未处理异常: {e.Exception}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
e.Handled = true;
|
2025-06-12 10:13:47 +08:00
|
|
|
|
|
2025-10-02 00:05:09 +08:00
|
|
|
|
SyncCrashActionFromSettings(); // 崩溃时同步最新设置
|
2025-07-06 15:39:37 +08:00
|
|
|
|
|
2025-06-13 09:19:44 +08:00
|
|
|
|
if (CrashAction == CrashActionType.SilentRestart && !IsAppExitByUser)
|
2025-06-12 10:13:47 +08:00
|
|
|
|
{
|
2025-06-23 14:33:58 +08:00
|
|
|
|
StartupCount.Increment();
|
|
|
|
|
|
if (StartupCount.GetCount() >= 5)
|
|
|
|
|
|
{
|
2026-02-23 14:31:48 +08:00
|
|
|
|
MessageBox.Show(Strings.GetString("Msg_RestartLimit"), Strings.GetString("Msg_RestartLimitTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
|
2025-06-23 14:33:58 +08:00
|
|
|
|
StartupCount.Reset();
|
|
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
2025-06-12 10:13:47 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
2025-07-29 02:33:19 +08:00
|
|
|
|
Process.Start(exePath);
|
2025-06-12 10:13:47 +08:00
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-06-12 10:13:47 +08:00
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
// CrashActionType.NoAction 时不做处理
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private TaskbarIcon _taskbar;
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理应用启动流程:根据命令行与设置显示启动画面、初始化组件与遥测、处理更新相关逻辑、单实例检查并在必要时通过 IPC 与已运行实例通信,最终创建并显示主窗口并启动文件关联与 IPC 监听器。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件的发送者(通常为 Application 对象)。</param>
|
|
|
|
|
|
/// <param name="e">启动事件参数;其 Args 可包含控制启动流程的标志,例如:
|
|
|
|
|
|
/// - "--final-app":表示这是更新后的最终应用启动(会清理更新标记等)
|
|
|
|
|
|
/// - "--update-mode":表示以更新模式启动(跳过主窗口显示)
|
|
|
|
|
|
/// - "--board":直接进入白板模式
|
|
|
|
|
|
/// - "--show":退出收纳模式并恢复浮动栏
|
|
|
|
|
|
/// - "--skip-mutex-check":跳过单实例互斥检查
|
|
|
|
|
|
/// - "-m":允许多实例启动
|
|
|
|
|
|
/// 另外也可能包含以 "icc:" 开头的 URI 参数或 .icstk 文件路径用于启动时的 IPC 交互。</param>
|
2025-10-01 18:17:39 +08:00
|
|
|
|
async void App_Startup(object sender, StartupEventArgs e)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-08-24 01:46:48 +08:00
|
|
|
|
appStartTime = DateTime.Now;
|
2026-03-07 12:13:29 +08:00
|
|
|
|
appStartupStartTime = DateTime.Now;
|
2026-04-30 15:26:47 +08:00
|
|
|
|
startupStopwatch.Restart();
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2026-04-30 15:04:54 +08:00
|
|
|
|
TryApplyPreferredLanguageFromSettings();
|
|
|
|
|
|
|
|
|
|
|
|
_pendingLocalizedResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
|
2026-04-18 22:32:08 +08:00
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 根据设置决定是否显示启动画面
|
2026-02-22 20:52:14 +08:00
|
|
|
|
if (ShouldShowSplashScreen() && !IsLaunchByFileOrUri(e.Args))
|
2025-10-01 18:38:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
ShowSplashScreen();
|
2026-02-23 14:31:48 +08:00
|
|
|
|
SetSplashMessage(Strings.GetString("Splash_Starting"));
|
2026-05-01 21:04:09 +08:00
|
|
|
|
SetSplashProgress(25);
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 强制刷新UI,确保启动画面显示
|
|
|
|
|
|
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
|
2025-10-01 18:38:01 +08:00
|
|
|
|
}
|
2025-10-01 18:17:39 +08:00
|
|
|
|
|
2026-04-30 15:04:54 +08:00
|
|
|
|
await Task.Delay(100);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version));
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 检查是否为最终应用启动(更新后的应用)
|
|
|
|
|
|
bool isFinalApp = e.Args.Contains("--final-app");
|
|
|
|
|
|
bool skipMutexCheck = e.Args.Contains("--skip-mutex-check");
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-13 14:59:47 +08:00
|
|
|
|
// 检查是否通过--board参数启动
|
|
|
|
|
|
bool hasBoardArg = e.Args.Contains("--board");
|
|
|
|
|
|
if (hasBoardArg)
|
|
|
|
|
|
{
|
|
|
|
|
|
StartWithBoardMode = true;
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 检测到--board参数,将直接进入白板模式");
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-10-02 15:30:51 +08:00
|
|
|
|
// 检查是否通过--show参数启动
|
|
|
|
|
|
bool hasShowArg = e.Args.Contains("--show");
|
|
|
|
|
|
if (hasShowArg)
|
|
|
|
|
|
{
|
|
|
|
|
|
StartWithShowMode = true;
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 检测到--show参数,将退出收纳模式并恢复浮动栏");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 记录最终应用启动状态
|
|
|
|
|
|
if (isFinalApp)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 检测到最终应用启动(更新后的应用)");
|
|
|
|
|
|
}
|
2025-07-29 18:37:00 +08:00
|
|
|
|
|
2025-10-18 18:26:13 +08:00
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
if (_isSplashScreenShown)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetSplashMessage("正在加载配置...");
|
2026-05-01 21:04:09 +08:00
|
|
|
|
SetSplashProgress(50);
|
|
|
|
|
|
await Task.Delay(100);
|
2026-02-13 00:09:38 +08:00
|
|
|
|
}
|
2025-07-26 14:29:24 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 处理更新模式启动
|
|
|
|
|
|
bool isUpdateMode = AutoUpdateHelper.HandleUpdateModeStartup(e.Args);
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 如果是更新模式,不显示主窗口但保持应用运行
|
|
|
|
|
|
if (isUpdateMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 检测到更新模式,跳过主窗口显示,保持应用运行");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 检查是否存在更新标记文件
|
2025-08-30 19:40:14 +08:00
|
|
|
|
string updateMarkerFile = Path.Combine(RootPath, "update_in_progress.tmp");
|
2025-08-30 14:11:39 +08:00
|
|
|
|
bool isUpdateInProgress = false;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 检查是否以更新模式启动
|
|
|
|
|
|
isUpdateMode = e.Args.Contains("--update-mode");
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 如果是最终应用启动,立即清理更新标记文件
|
|
|
|
|
|
if (isFinalApp)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (File.Exists(updateMarkerFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(updateMarkerFile);
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 最终应用启动,清理更新标记文件");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
2025-11-02 11:27:46 +08:00
|
|
|
|
|
2026-05-01 20:37:39 +08:00
|
|
|
|
_ = Task.Run(async () =>
|
2025-11-02 11:27:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(3000);
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 最终应用启动,删除AutoUpdate文件夹");
|
|
|
|
|
|
AutoUpdateHelper.DeleteUpdatesFolder();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 删除AutoUpdate文件夹失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-08-30 15:49:37 +08:00
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 如果不是最终应用启动,才检查更新标记文件
|
|
|
|
|
|
if (!isFinalApp && File.Exists(updateMarkerFile))
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-08-03 16:46:33 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
string updateProcessIdStr = File.ReadAllText(updateMarkerFile).Trim();
|
|
|
|
|
|
if (int.TryParse(updateProcessIdStr, out int updateProcessId))
|
2025-07-06 15:39:37 +08:00
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"App | 检测到更新标记文件,更新进程ID: {updateProcessId}");
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 检查更新进程是否还在运行
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Process updateProcess = Process.GetProcessById(updateProcessId);
|
|
|
|
|
|
if (!updateProcess.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 更新进程仍在运行,等待更新完成");
|
|
|
|
|
|
isUpdateInProgress = true;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 等待更新进程完成
|
|
|
|
|
|
int waitCount = 0;
|
|
|
|
|
|
const int maxWaitCount = 10; // 减少等待时间到10秒
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
while (waitCount < maxWaitCount && !updateProcess.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
Thread.Sleep(500); // 减少等待间隔到500ms
|
|
|
|
|
|
waitCount++;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 等待更新进程完成... ({waitCount}/{maxWaitCount})");
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
if (updateProcess.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 更新进程已结束");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 等待更新进程超时,强制清理", LogHelper.LogType.Warning);
|
|
|
|
|
|
// 超时后强制清理标记文件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (File.Exists(updateMarkerFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(updateMarkerFile);
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 强制清理更新标记文件");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 强制清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 更新进程已结束");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (ArgumentException)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 更新进程已不存在");
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 无论更新进程是否还在运行,都清理标记文件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (File.Exists(updateMarkerFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(updateMarkerFile);
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 清理更新标记文件");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
2025-07-06 15:39:37 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
2025-08-30 14:11:39 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 读取更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
// 如果读取失败,也尝试删除标记文件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (File.Exists(updateMarkerFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(updateMarkerFile);
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 清理损坏的更新标记文件");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
2025-08-30 14:11:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 如果是更新过程、更新模式、最终应用或跳过Mutex检查,跳过Mutex检查
|
|
|
|
|
|
if (!isUpdateInProgress && !isUpdateMode && !isFinalApp && !skipMutexCheck)
|
2025-08-30 14:11:39 +08:00
|
|
|
|
{
|
|
|
|
|
|
bool ret;
|
|
|
|
|
|
mutex = new Mutex(true, "InkCanvasForClass CE", out ret);
|
|
|
|
|
|
|
|
|
|
|
|
if (!ret && !e.Args.Contains("-m")) //-m multiple
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.NewLog("Detected existing instance");
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-08-31 11:49:00 +08:00
|
|
|
|
// 检查是否有.icstk文件参数
|
|
|
|
|
|
string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(e.Args);
|
|
|
|
|
|
if (!string.IsNullOrEmpty(icstkFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"检测到已运行实例,尝试通过IPC发送文件: {icstkFile}", LogHelper.LogType.Event);
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-08-31 11:49:00 +08:00
|
|
|
|
// 尝试通过IPC发送文件路径给已运行实例
|
|
|
|
|
|
if (FileAssociationManager.TrySendFileToExistingInstance(icstkFile))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("文件路径已通过IPC发送给已运行实例", LogHelper.LogType.Event);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("通过IPC发送文件路径失败", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-13 14:59:47 +08:00
|
|
|
|
// 检查是否有--board参数
|
|
|
|
|
|
else if (hasBoardArg)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("检测到已运行实例且有--board参数,尝试通过IPC发送白板模式命令", LogHelper.LogType.Event);
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试通过IPC发送白板模式命令给已运行实例
|
|
|
|
|
|
if (FileAssociationManager.TrySendBoardModeCommandToExistingInstance())
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("白板模式命令已通过IPC发送给已运行实例", LogHelper.LogType.Event);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("通过IPC发送白板模式命令失败", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-02 15:30:51 +08:00
|
|
|
|
// 检查是否有--show参数
|
|
|
|
|
|
else if (hasShowArg)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("检测到已运行实例且有--show参数,尝试通过IPC发送展开浮动栏命令", LogHelper.LogType.Event);
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试通过IPC发送展开浮动栏命令给已运行实例
|
|
|
|
|
|
if (FileAssociationManager.TrySendShowModeCommandToExistingInstance())
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("展开浮动栏命令已通过IPC发送给已运行实例", LogHelper.LogType.Event);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("通过IPC发送展开浮动栏命令失败", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-04 11:09:49 +08:00
|
|
|
|
// 检查是否有URI参数
|
|
|
|
|
|
else if (e.Args.Any(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase)))
|
|
|
|
|
|
{
|
|
|
|
|
|
string uriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
LogHelper.WriteLogToFile($"检测到已运行实例且有URI参数: {uriArg}", LogHelper.LogType.Event);
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试通过IPC发送URI命令给已运行实例
|
|
|
|
|
|
if (FileAssociationManager.TrySendUriCommandToExistingInstance(uriArg))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("URI命令已通过IPC发送给已运行实例", LogHelper.LogType.Event);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("通过IPC发送URI命令失败", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-31 11:49:00 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event);
|
|
|
|
|
|
}
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
LogHelper.NewLog("Ink Canvas automatically closed");
|
|
|
|
|
|
IsAppExitByUser = true; // 多开时标记为用户主动退出
|
|
|
|
|
|
// 写入退出信号,确保看门狗不会重启
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
StartupCount.Reset();
|
|
|
|
|
|
File.WriteAllText(watchdogExitSignalFile, "exit");
|
|
|
|
|
|
if (watchdogProcess != null && !watchdogProcess.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
watchdogProcess.Kill();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-08-30 14:11:39 +08:00
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isUpdateMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 更新模式启动,跳过重复运行检测");
|
|
|
|
|
|
}
|
2025-08-30 15:49:37 +08:00
|
|
|
|
else if (isFinalApp)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 最终应用启动,跳过重复运行检测");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (skipMutexCheck)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 跳过Mutex检查模式启动,跳过重复运行检测");
|
|
|
|
|
|
}
|
2025-08-30 14:11:39 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("App | 更新过程中,跳过重复运行检测");
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 15:49:37 +08:00
|
|
|
|
// 在特殊模式下,创建一个临时的Mutex以避免其他检查出错
|
|
|
|
|
|
string mutexName = isFinalApp ? "InkCanvasForClass CE Final" : "InkCanvasForClass CE Update";
|
|
|
|
|
|
mutex = new Mutex(true, mutexName, out bool tempRet);
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 额外等待一小段时间确保更新进程完全退出
|
2026-02-12 17:22:47 +08:00
|
|
|
|
await Task.Delay(1000);
|
2025-08-30 15:49:37 +08:00
|
|
|
|
LogHelper.WriteLogToFile("App | 特殊模式等待完成,继续启动");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon");
|
|
|
|
|
|
|
|
|
|
|
|
StartArgs = e.Args;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 在非更新模式下创建主窗口
|
2025-10-01 18:38:01 +08:00
|
|
|
|
if (_isSplashScreenShown)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetSplashMessage("正在初始化主界面...");
|
2026-05-01 21:04:09 +08:00
|
|
|
|
SetSplashProgress(75);
|
2025-10-03 17:08:46 +08:00
|
|
|
|
}
|
2025-08-30 14:11:39 +08:00
|
|
|
|
var mainWindow = new MainWindow();
|
|
|
|
|
|
MainWindow = mainWindow;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2026-04-10 01:24:57 +08:00
|
|
|
|
// 注册 InkCanvas 服务供插件使用
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var inkCanvasService = new Plugins.InkCanvasService(mainWindow);
|
|
|
|
|
|
Plugins.PluginManager.Instance.RegisterService<Plugins.IInkCanvasService>(inkCanvasService);
|
|
|
|
|
|
LogHelper.WriteLogToFile("InkCanvasService registered for plugins");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"Failed to register InkCanvasService: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 22:09:27 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var appRestartService = new Plugins.AppRestartService();
|
|
|
|
|
|
Plugins.PluginManager.Instance.RegisterService<Plugins.IAppRestartService>(appRestartService);
|
|
|
|
|
|
LogHelper.WriteLogToFile("AppRestartService registered for plugins");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"Failed to register AppRestartService: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 主窗口加载完成后关闭启动画面
|
2025-10-01 18:17:39 +08:00
|
|
|
|
mainWindow.Loaded += (s, args) =>
|
|
|
|
|
|
{
|
2026-01-17 16:32:57 +08:00
|
|
|
|
isStartupComplete = true;
|
2026-03-07 12:13:29 +08:00
|
|
|
|
startupCompleteHeartbeat = DateTime.Now;
|
2026-04-30 15:26:47 +08:00
|
|
|
|
if (_isSplashScreenShown && splashStopwatch.IsRunning)
|
2026-02-14 21:40:01 +08:00
|
|
|
|
{
|
2026-04-30 15:26:47 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"启动完成心跳已记录,启动画面显示时长: {splashStopwatch.Elapsed.TotalSeconds:F2}秒");
|
2026-02-14 21:40:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"启动完成心跳已记录");
|
|
|
|
|
|
}
|
2026-04-30 15:26:47 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"启动时长: {startupStopwatch.Elapsed.TotalSeconds:F2}秒");
|
2026-03-21 16:13:42 +08:00
|
|
|
|
|
2025-10-01 18:38:01 +08:00
|
|
|
|
if (_isSplashScreenShown)
|
2025-10-01 18:17:39 +08:00
|
|
|
|
{
|
2026-05-01 21:04:09 +08:00
|
|
|
|
SetSplashMessage("启动完成!");
|
|
|
|
|
|
SetSplashProgress(100);
|
|
|
|
|
|
Task.Delay(100).ContinueWith(_ =>
|
2025-10-01 18:38:01 +08:00
|
|
|
|
{
|
2025-10-02 19:20:03 +08:00
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 延迟关闭启动画面,让用户看到完成消息
|
2026-05-01 21:04:09 +08:00
|
|
|
|
Task.Delay(100).ContinueWith(__ =>
|
2025-10-02 19:20:03 +08:00
|
|
|
|
{
|
2026-01-17 16:32:57 +08:00
|
|
|
|
Dispatcher.Invoke(() => CloseSplashScreen());
|
2025-10-02 19:20:03 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-10-01 18:38:01 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-10-01 18:17:39 +08:00
|
|
|
|
};
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
mainWindow.Show();
|
2026-04-30 15:04:54 +08:00
|
|
|
|
_ = Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(600);
|
|
|
|
|
|
Dispatcher.Invoke(() => _taskbar?.ForceCreate());
|
|
|
|
|
|
});
|
2026-05-01 20:37:39 +08:00
|
|
|
|
_ = Dispatcher.BeginInvoke(new Action(() =>
|
2026-04-30 15:04:54 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (_pendingLocalizedResourceSet != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
LoadLocalizedResources(_pendingLocalizedResourceSet);
|
|
|
|
|
|
_pendingLocalizedResourceSet = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}), DispatcherPriority.ApplicationIdle);
|
2025-06-17 13:20:06 +08:00
|
|
|
|
|
2026-02-04 11:09:49 +08:00
|
|
|
|
// 处理启动时的URI参数
|
|
|
|
|
|
string startupUriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (!string.IsNullOrEmpty(startupUriArg))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 处理启动URI参数: {startupUriArg}", LogHelper.LogType.Event);
|
|
|
|
|
|
// 延迟一点执行,确保窗口初始化完成
|
2026-05-01 20:37:39 +08:00
|
|
|
|
_ = Task.Delay(1000).ContinueWith(_ =>
|
2026-02-04 11:09:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
mainWindow.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
mainWindow.HandleUriCommand(startupUriArg);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 14:42:24 +08:00
|
|
|
|
_ = RunDeferredStartupTasksAsync();
|
2025-08-31 11:49:00 +08:00
|
|
|
|
|
2026-04-30 14:42:24 +08:00
|
|
|
|
}
|
2025-08-31 11:49:00 +08:00
|
|
|
|
|
2026-04-30 14:42:24 +08:00
|
|
|
|
private async Task RunDeferredStartupTasksAsync()
|
|
|
|
|
|
{
|
2026-02-22 23:53:12 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-04-30 14:42:24 +08:00
|
|
|
|
await Task.Delay(1200);
|
2026-02-22 23:53:12 +08:00
|
|
|
|
|
2026-04-30 15:04:54 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
IACoreDllExtractor.ExtractIACoreDlls();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 14:42:24 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
|
|
|
|
|
FileAssociationManager.RegisterFileAssociation();
|
|
|
|
|
|
FileAssociationManager.ShowFileAssociationStatus();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("启动IPC监听器");
|
|
|
|
|
|
FileAssociationManager.StartIpcListener();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("初始化上传帮助类");
|
|
|
|
|
|
Helpers.UploadHelper.Initialize();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("开始加载插件");
|
|
|
|
|
|
await PluginManager.Instance.LoadAllAsync();
|
|
|
|
|
|
LogHelper.WriteLogToFile(string.Format("插件加载完成,共加载 {0} 个插件", PluginManager.Instance.Plugins.Count));
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile(string.Format("加载插件时出错: {0}", ex.Message), LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
2026-04-30 15:38:21 +08:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(1500);
|
|
|
|
|
|
DeviceIdentifier.RecordAppLaunch();
|
|
|
|
|
|
var systemVersion = DeviceIdentifier.GetSystemVersion();
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(systemVersion))
|
|
|
|
|
|
{
|
|
|
|
|
|
SentrySdk.ConfigureScope(scope =>
|
|
|
|
|
|
{
|
|
|
|
|
|
scope.SetTag("system_version", systemVersion);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 初始化设备统计与遥测标签失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
2026-04-10 01:24:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-04-30 14:42:24 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"启动阶段任务执行失败: {ex.Message}", LogHelper.LogType.Error);
|
2026-04-10 01:24:57 +08:00
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 15:04:54 +08:00
|
|
|
|
private void TryApplyPreferredLanguageFromSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
|
|
|
|
|
|
if (!File.Exists(settingsPath)) return;
|
|
|
|
|
|
|
|
|
|
|
|
var json = File.ReadAllText(settingsPath);
|
|
|
|
|
|
dynamic obj = JsonConvert.DeserializeObject(json);
|
|
|
|
|
|
string preferredLanguage = obj?["appearance"]?["language"]?.ToString();
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(preferredLanguage))
|
|
|
|
|
|
{
|
|
|
|
|
|
LocalizationHelper.TrySetCulture(preferredLanguage);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"启动时预加载语言失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void LoadLocalizedResources(System.Resources.ResourceSet resourceSet)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (System.Collections.DictionaryEntry entry in resourceSet)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entry.Key is string key && entry.Value is string value)
|
|
|
|
|
|
Current.Resources[key] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
if (SystemInformation.MouseWheelScrollLines == -1)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
e.Handled = false;
|
|
|
|
|
|
else
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
ScrollViewerEx SenderScrollViewer = (ScrollViewerEx)sender;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
SenderScrollViewer.ScrollToVerticalOffset(SenderScrollViewer.VerticalOffset - e.Delta * 10 * SystemInformation.MouseWheelScrollLines / (double)120);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-06-12 10:13:47 +08:00
|
|
|
|
|
2025-10-01 18:45:26 +08:00
|
|
|
|
// 用于设置崩溃后操作类型
|
2025-06-12 10:13:47 +08:00
|
|
|
|
public enum CrashActionType
|
|
|
|
|
|
{
|
|
|
|
|
|
SilentRestart,
|
|
|
|
|
|
NoAction
|
|
|
|
|
|
}
|
2025-06-12 10:34:29 +08:00
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 心跳相关
|
2026-02-12 17:22:47 +08:00
|
|
|
|
private static DispatcherTimer heartbeatTimer;
|
2026-03-07 12:13:29 +08:00
|
|
|
|
private static DateTime lastHeartbeat = DateTime.Now;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
private static Timer watchdogTimer;
|
2026-01-17 16:32:57 +08:00
|
|
|
|
private static bool isStartupComplete = false;
|
2026-03-07 12:13:29 +08:00
|
|
|
|
private static DateTime startupCompleteHeartbeat = DateTime.MinValue;
|
|
|
|
|
|
private static DateTime splashScreenStartTime = DateTime.MinValue;
|
|
|
|
|
|
private static DateTime appStartupStartTime = DateTime.MinValue;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 启动并管理应用的心跳与守护检查定时器,监测启动阶段与主线程是否无响应,并在符合配置的情况下尝试静默重启应用。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 启动一个每秒更新心跳时间戳的调度定时器和一个每3秒运行的守护定时器。
|
|
|
|
|
|
/// - 守护定时器在首次运行的启动阶段若检测到超过两分钟未完成启动,会根据 CrashAction 配置尝试静默重启。
|
|
|
|
|
|
/// - 在启动完成后若检测到主线程超过10秒无响应,会根据 CrashAction 配置尝试静默重启。
|
|
|
|
|
|
/// - 对连续重启次数有保护:若重启计数达到或超过5次,会弹出提示并停止自动重启(重置重启计数并退出进程)。
|
|
|
|
|
|
/// - 在 OOBE(首次引导)展示期间不执行守护检查。
|
|
|
|
|
|
/// - 该方法会产生外部可观察的副作用:可能启动新进程并调用 Environment.Exit 终止当前进程,或显示消息框。
|
|
|
|
|
|
/// </remarks>
|
2025-06-12 10:34:29 +08:00
|
|
|
|
private void StartHeartbeatMonitor()
|
|
|
|
|
|
{
|
2026-02-12 17:22:47 +08:00
|
|
|
|
heartbeatTimer = new DispatcherTimer
|
|
|
|
|
|
{
|
|
|
|
|
|
Interval = TimeSpan.FromSeconds(1)
|
|
|
|
|
|
};
|
2026-03-07 12:13:29 +08:00
|
|
|
|
heartbeatTimer.Tick += (_, __) => lastHeartbeat = DateTime.Now;
|
2026-02-12 17:22:47 +08:00
|
|
|
|
heartbeatTimer.Start();
|
|
|
|
|
|
|
2025-06-12 10:34:29 +08:00
|
|
|
|
watchdogTimer = new Timer(_ =>
|
|
|
|
|
|
{
|
2026-02-14 15:59:10 +08:00
|
|
|
|
if (IsOobeShowing)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2026-01-19 18:16:03 +08:00
|
|
|
|
if (!isStartupComplete && appStartupStartTime != DateTime.MinValue)
|
2025-06-12 10:34:29 +08:00
|
|
|
|
{
|
2026-03-21 16:13:42 +08:00
|
|
|
|
DateTime startTime = _isSplashScreenShown && splashScreenStartTime != DateTime.MinValue
|
|
|
|
|
|
? splashScreenStartTime
|
2026-01-19 18:16:03 +08:00
|
|
|
|
: appStartupStartTime;
|
2026-03-07 12:13:29 +08:00
|
|
|
|
TimeSpan elapsedSinceStart = DateTime.Now - startTime;
|
2026-03-05 11:30:07 +08:00
|
|
|
|
if (elapsedSinceStart.TotalMinutes >= 2)
|
2026-01-10 18:25:02 +08:00
|
|
|
|
{
|
2026-01-19 18:16:03 +08:00
|
|
|
|
string timeType = _isSplashScreenShown ? "启动画面已显示" : "应用启动开始";
|
|
|
|
|
|
LogHelper.WriteLogToFile($"检测到启动假死:{timeType}{elapsedSinceStart.TotalMinutes:F2}分钟,但未收到启动完成心跳,自动重启。", LogHelper.LogType.Error);
|
|
|
|
|
|
SyncCrashActionFromSettings();
|
|
|
|
|
|
if (CrashAction == CrashActionType.SilentRestart)
|
2025-06-23 14:33:58 +08:00
|
|
|
|
{
|
2026-01-19 18:16:03 +08:00
|
|
|
|
StartupCount.Increment();
|
|
|
|
|
|
if (StartupCount.GetCount() >= 5)
|
2026-01-10 18:25:02 +08:00
|
|
|
|
{
|
2026-02-23 14:31:48 +08:00
|
|
|
|
MessageBox.Show(Strings.GetString("Msg_RestartLimit"), Strings.GetString("Msg_RestartLimitTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
|
2026-01-19 18:16:03 +08:00
|
|
|
|
StartupCount.Reset();
|
2026-01-10 18:25:02 +08:00
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
2026-01-19 18:16:03 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
|
|
|
|
|
Process.Start(exePath);
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2026-01-19 18:16:03 +08:00
|
|
|
|
Environment.Exit(1);
|
2025-06-23 14:33:58 +08:00
|
|
|
|
}
|
2026-01-19 18:16:03 +08:00
|
|
|
|
return;
|
2025-06-12 10:34:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-14 16:04:01 +08:00
|
|
|
|
|
|
|
|
|
|
if (isStartupComplete)
|
2026-01-10 18:25:02 +08:00
|
|
|
|
{
|
2026-03-14 16:04:01 +08:00
|
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
var sinceHeartbeat = now - lastHeartbeat;
|
|
|
|
|
|
var sinceStartupComplete = startupCompleteHeartbeat == DateTime.MinValue
|
|
|
|
|
|
? TimeSpan.Zero
|
|
|
|
|
|
: now - startupCompleteHeartbeat;
|
|
|
|
|
|
|
|
|
|
|
|
if (sinceStartupComplete.TotalSeconds < 30)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (sinceHeartbeat.TotalSeconds > 10)
|
2026-01-17 16:32:57 +08:00
|
|
|
|
{
|
2026-03-14 16:04:01 +08:00
|
|
|
|
LogHelper.NewLog("检测到主线程无响应,自动重启。");
|
|
|
|
|
|
SyncCrashActionFromSettings();
|
|
|
|
|
|
if (CrashAction == CrashActionType.SilentRestart)
|
2026-01-17 16:32:57 +08:00
|
|
|
|
{
|
2026-03-14 16:04:01 +08:00
|
|
|
|
StartupCount.Increment();
|
|
|
|
|
|
if (StartupCount.GetCount() >= 5)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(Strings.GetString("Msg_RestartLimit"), Strings.GetString("Msg_RestartLimitTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
StartupCount.Reset();
|
|
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
|
|
|
|
|
Process.Start(exePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2026-01-17 16:32:57 +08:00
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-10 18:25:02 +08:00
|
|
|
|
}
|
2025-06-12 10:34:29 +08:00
|
|
|
|
}, null, 0, 3000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 看门狗进程
|
2025-10-25 19:57:56 +08:00
|
|
|
|
public static void StartWatchdogIfNeeded()
|
2025-06-12 10:34:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 避免递归启动
|
|
|
|
|
|
if (Environment.GetCommandLineArgs().Contains("--watchdog")) return;
|
|
|
|
|
|
// 启动看门狗进程
|
|
|
|
|
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
|
|
|
|
|
var psi = new ProcessStartInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
FileName = exePath,
|
|
|
|
|
|
Arguments = "--watchdog " + Process.GetCurrentProcess().Id + " \"" + watchdogExitSignalFile + "\"",
|
|
|
|
|
|
CreateNoWindow = true,
|
|
|
|
|
|
UseShellExecute = false,
|
|
|
|
|
|
WindowStyle = ProcessWindowStyle.Hidden
|
|
|
|
|
|
};
|
|
|
|
|
|
watchdogProcess = Process.Start(psi);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 作为守护进程监视指定的主进程,并在主进程异常退出时根据配置执行重启或退出操作。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 该方法期望命令行参数格式为:"--watchdog <pid> <exitSignalFile>"(args[1..3])。
|
|
|
|
|
|
/// - 每 2 秒检查一次指定的主进程是否仍在运行;同时检测退出信号文件,若存在则删除该文件并以代码 0 退出守护进程。
|
|
|
|
|
|
/// - 当主进程退出时,会同步崩溃处理设置(SyncCrashActionFromSettings)。若启用了 UIA 顶层访问(IsUIAccessTopMostEnabled),守护进程直接退出。
|
|
|
|
|
|
/// - 若崩溃动作为 SilentRestart,则增加启动计数并:当连续重启计数达到 5 次及以上时弹出错误对话框、重置计数并以代码 1 退出;否则启动新的主进程实例。
|
|
|
|
|
|
/// 方法对内部异常静默处理,并在完成后确保进程退出。
|
|
|
|
|
|
/// </remarks>
|
2025-06-12 10:34:29 +08:00
|
|
|
|
public static void RunWatchdogIfNeeded()
|
|
|
|
|
|
{
|
|
|
|
|
|
var args = Environment.GetCommandLineArgs();
|
|
|
|
|
|
if (args.Length >= 4 && args[1] == "--watchdog")
|
|
|
|
|
|
{
|
|
|
|
|
|
int pid = int.Parse(args[2]);
|
|
|
|
|
|
string exitSignalFile = args[3];
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var proc = Process.GetProcessById(pid);
|
|
|
|
|
|
while (!proc.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查退出信号文件
|
|
|
|
|
|
if (File.Exists(exitSignalFile))
|
|
|
|
|
|
{
|
2026-02-21 16:51:34 +08:00
|
|
|
|
try { File.Delete(exitSignalFile); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-06-12 10:34:29 +08:00
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
Thread.Sleep(2000);
|
|
|
|
|
|
}
|
2025-07-06 15:39:37 +08:00
|
|
|
|
// 主进程异常退出,自动重启前判断崩溃后操作
|
2025-10-18 18:26:13 +08:00
|
|
|
|
SyncCrashActionFromSettings(); // 同步设置
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2025-10-18 18:26:13 +08:00
|
|
|
|
if (IsUIAccessTopMostEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
}
|
2025-12-20 13:56:46 +08:00
|
|
|
|
|
2025-07-06 15:39:37 +08:00
|
|
|
|
if (CrashAction == CrashActionType.SilentRestart)
|
2025-06-23 14:33:58 +08:00
|
|
|
|
{
|
2025-07-06 15:39:37 +08:00
|
|
|
|
StartupCount.Increment();
|
|
|
|
|
|
if (StartupCount.GetCount() >= 5)
|
|
|
|
|
|
{
|
2026-02-23 14:31:48 +08:00
|
|
|
|
MessageBox.Show(Strings.GetString("Msg_RestartLimit"), Strings.GetString("Msg_RestartLimitTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
StartupCount.Reset();
|
|
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
2026-02-15 02:33:42 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
|
|
|
|
|
Process.Start(exePath);
|
|
|
|
|
|
}
|
2025-06-23 14:33:58 +08:00
|
|
|
|
}
|
2025-06-12 10:34:29 +08:00
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-06-12 10:34:29 +08:00
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 22:47:41 +08:00
|
|
|
|
internal static string GetDlassTelemetryDsn()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var envDsn = Environment.GetEnvironmentVariable("DLASS_SENTRY_DSN");
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(envDsn))
|
|
|
|
|
|
{
|
|
|
|
|
|
return envDsn;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:16:37 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
|
|
|
|
var resourceName = "Ink_Canvas.telemetry_dsn.txt";
|
|
|
|
|
|
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (stream != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.UTF8))
|
|
|
|
|
|
{
|
|
|
|
|
|
string dsn = reader.ReadToEnd().Trim();
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(dsn))
|
|
|
|
|
|
{
|
|
|
|
|
|
return dsn;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"从程序集资源读取遥测 DSN 失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 10:43:25 +08:00
|
|
|
|
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
|
|
|
|
|
|
string currentDir = Path.GetDirectoryName(assemblyLocation);
|
2026-03-21 16:13:42 +08:00
|
|
|
|
|
2026-02-07 10:43:25 +08:00
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
string dsnFilePath = Path.Combine(currentDir, "telemetry_dsn.txt");
|
|
|
|
|
|
if (File.Exists(dsnFilePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
string dsn = File.ReadAllText(dsnFilePath, System.Text.Encoding.UTF8).Trim();
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(dsn))
|
|
|
|
|
|
{
|
|
|
|
|
|
return dsn;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-21 16:13:42 +08:00
|
|
|
|
|
2026-02-07 10:43:25 +08:00
|
|
|
|
DirectoryInfo parentDir = Directory.GetParent(currentDir);
|
|
|
|
|
|
if (parentDir == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
currentDir = parentDir.FullName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
2026-02-06 22:47:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 10:34:29 +08:00
|
|
|
|
private void App_Exit(object sender, ExitEventArgs e)
|
|
|
|
|
|
{
|
2026-04-30 14:50:57 +08:00
|
|
|
|
CleanupTerminationMonitoring();
|
2026-04-10 01:24:57 +08:00
|
|
|
|
// 卸载所有插件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("正在卸载插件...");
|
|
|
|
|
|
PluginManager.Instance.UnloadAll();
|
|
|
|
|
|
LogHelper.WriteLogToFile("插件卸载完成");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 仅在软件内主动退出时关闭看门狗,并写入退出信号
|
2025-06-12 10:34:29 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 记录应用退出状态
|
2025-07-15 16:06:29 +08:00
|
|
|
|
string exitType = IsAppExitByUser ? "用户主动退出" : "应用程序退出";
|
|
|
|
|
|
WriteCrashLog($"{exitType},退出代码: {e.ApplicationExitCode}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 记录应用退出(设备标识符)
|
2025-07-26 14:29:24 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
DeviceIdentifier.RecordAppExit();
|
|
|
|
|
|
LogHelper.WriteLogToFile($"App | 应用运行时长: {(DateTime.Now - appStartTime).TotalMinutes:F1}分钟");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception deviceEx)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"记录设备标识符退出信息失败: {deviceEx.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-12 10:34:29 +08:00
|
|
|
|
if (IsAppExitByUser)
|
|
|
|
|
|
{
|
2026-04-08 12:44:00 +08:00
|
|
|
|
// 写入退出信号文件,通知看门狗正常退出
|
|
|
|
|
|
StartupCount.Reset();
|
|
|
|
|
|
File.WriteAllText(watchdogExitSignalFile, "exit");
|
|
|
|
|
|
if (watchdogProcess != null && !watchdogProcess.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
watchdogProcess.Kill();
|
|
|
|
|
|
}
|
2025-06-12 10:34:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
catch (Exception ex)
|
2025-07-15 16:06:29 +08:00
|
|
|
|
{
|
2026-01-17 16:32:57 +08:00
|
|
|
|
// 尝试记录最后的错误
|
2025-07-15 16:06:29 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"退出处理时发生错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
2025-07-15 16:06:29 +08:00
|
|
|
|
}
|
2025-06-12 10:34:29 +08:00
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2026-03-05 11:30:07 +08:00
|
|
|
|
}
|