diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index b662a247..0808e85a 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -633,7 +633,28 @@ namespace Ink_Canvas if (!ret && !e.Args.Contains("-m")) //-m multiple { LogHelper.NewLog("Detected existing instance"); - MessageBox.Show("已有一个程序实例正在运行"); + + // 检查是否有.icstk文件参数 + string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(e.Args); + if (!string.IsNullOrEmpty(icstkFile)) + { + LogHelper.WriteLogToFile($"检测到已运行实例,尝试通过IPC发送文件: {icstkFile}", LogHelper.LogType.Event); + + // 尝试通过IPC发送文件路径给已运行实例 + if (FileAssociationManager.TrySendFileToExistingInstance(icstkFile)) + { + LogHelper.WriteLogToFile("文件路径已通过IPC发送给已运行实例", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("通过IPC发送文件路径失败", LogHelper.LogType.Warning); + } + } + else + { + LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event); + } + LogHelper.NewLog("Ink Canvas automatically closed"); IsAppExitByUser = true; // 多开时标记为用户主动退出 // 写入退出信号,确保看门狗不会重启 @@ -687,6 +708,29 @@ namespace Ink_Canvas MainWindow = mainWindow; mainWindow.Show(); + // 新增:注册.icstk文件关联 + try + { + LogHelper.WriteLogToFile("开始注册.icstk文件关联"); + FileAssociationManager.RegisterFileAssociation(); + FileAssociationManager.ShowFileAssociationStatus(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error); + } + + // 新增:启动IPC监听器 + try + { + LogHelper.WriteLogToFile("启动IPC监听器"); + FileAssociationManager.StartIpcListener(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error); + } + // 新增:Office注册表检测 try { diff --git a/Ink Canvas/Helpers/FileAssociationManager.cs b/Ink Canvas/Helpers/FileAssociationManager.cs new file mode 100644 index 00000000..e950e521 --- /dev/null +++ b/Ink Canvas/Helpers/FileAssociationManager.cs @@ -0,0 +1,380 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows; +using Microsoft.Win32; +using System.Threading; +using System.Text; + +namespace Ink_Canvas.Helpers +{ + /// + /// 文件关联管理器,用于注册和处理.icstk文件的关联 + /// + public static class FileAssociationManager + { + private const string FileExtension = ".icstk"; + private const string FileTypeName = "InkCanvasStrokesFile"; + private const string AppName = "Ink Canvas"; + private const string AppDescription = "Ink Canvas Strokes File"; + + // IPC相关常量 + private const string IpcMutexName = "InkCanvasFileAssociationIpc"; + private const string IpcEventName = "InkCanvasFileAssociationEvent"; + private const string IpcFilePrefix = "InkCanvasFileAssociation_"; + private const int IpcTimeout = 5000; // 5秒超时 + + /// + /// 注册.icstk文件关联 + /// + public static bool RegisterFileAssociation() + { + try + { + string exePath = Process.GetCurrentProcess().MainModule.FileName; + + // 注册文件类型 + using (RegistryKey fileTypeKey = Registry.ClassesRoot.CreateSubKey(FileTypeName)) + { + fileTypeKey.SetValue("", AppDescription); + fileTypeKey.SetValue("FriendlyTypeName", AppDescription); + + // 设置默认图标 + using (RegistryKey defaultIconKey = fileTypeKey.CreateSubKey("DefaultIcon")) + { + defaultIconKey.SetValue("", $"\"{exePath}\",0"); + } + + // 设置打开命令 + using (RegistryKey shellKey = fileTypeKey.CreateSubKey("shell")) + using (RegistryKey openKey = shellKey.CreateSubKey("open")) + using (RegistryKey commandKey = openKey.CreateSubKey("command")) + { + commandKey.SetValue("", $"\"{exePath}\" \"%1\""); + } + } + + // 注册文件扩展名 + using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension)) + { + extensionKey.SetValue("", FileTypeName); + } + + // 刷新系统文件关联缓存 + RefreshSystemFileAssociations(); + + LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event); + return true; + } + catch (SecurityException ex) + { + LogHelper.WriteLogToFile($"注册文件关联时权限不足: {ex.Message}", LogHelper.LogType.Error); + return false; + } + catch (UnauthorizedAccessException ex) + { + LogHelper.WriteLogToFile($"注册文件关联时访问被拒绝: {ex.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 注销.icstk文件关联 + /// + public static bool UnregisterFileAssociation() + { + try + { + // 删除文件扩展名关联 + Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false); + + // 删除文件类型定义 + Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false); + + // 刷新系统文件关联缓存 + RefreshSystemFileAssociations(); + + LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"注销文件关联时出错: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 检查文件关联是否已注册 + /// + public static bool IsFileAssociationRegistered() + { + try + { + using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(FileExtension)) + { + if (extensionKey == null) return false; + + string fileType = extensionKey.GetValue("") as string; + if (string.IsNullOrEmpty(fileType)) return false; + + using (RegistryKey fileTypeKey = Registry.ClassesRoot.OpenSubKey(fileType)) + { + if (fileTypeKey == null) return false; + + using (RegistryKey shellKey = fileTypeKey.OpenSubKey("shell\\open\\command")) + { + if (shellKey == null) return false; + + string command = shellKey.GetValue("") as string; + if (string.IsNullOrEmpty(command)) return false; + + // 检查命令是否指向当前应用程序 + string currentExePath = Process.GetCurrentProcess().MainModule.FileName; + return command.Contains(currentExePath); + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error); + } + + return false; + } + + /// + /// 显示文件关联状态 + /// + public static void ShowFileAssociationStatus() + { + bool isRegistered = IsFileAssociationRegistered(); + LogHelper.WriteLogToFile($"{FileExtension}文件关联状态: {(isRegistered ? "已注册" : "未注册")}", LogHelper.LogType.Event); + } + + /// + /// 刷新系统文件关联缓存 + /// + private static void RefreshSystemFileAssociations() + { + try + { + // 通知系统文件关联已更改 + SHChangeNotify(0x08000000, 0, IntPtr.Zero, IntPtr.Zero); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"刷新文件关联缓存时出错: {ex.Message}", LogHelper.LogType.Warning); + } + } + + /// + /// 处理命令行参数中的文件路径 + /// + /// 命令行参数 + /// 找到的.icstk文件路径,如果没有找到则返回null + public static string GetIcstkFileFromArgs(string[] args) + { + if (args == null || args.Length == 0) return null; + + foreach (string arg in args) + { + if (string.IsNullOrEmpty(arg)) continue; + + // 检查是否为.icstk文件 + if (Path.GetExtension(arg).Equals(FileExtension, StringComparison.OrdinalIgnoreCase)) + { + // 检查文件是否存在 + if (File.Exists(arg)) + { + LogHelper.WriteLogToFile($"从命令行参数中找到.icstk文件: {arg}", LogHelper.LogType.Event); + return arg; + } + else + { + LogHelper.WriteLogToFile($"命令行参数中的.icstk文件不存在: {arg}", LogHelper.LogType.Warning); + } + } + } + + return null; + } + + /// + /// 尝试通过IPC将文件路径发送给已运行的实例 + /// + /// 要打开的文件路径 + /// 是否成功发送 + public static bool TrySendFileToExistingInstance(string filePath) + { + try + { + LogHelper.WriteLogToFile($"尝试通过IPC发送文件路径给已运行实例: {filePath}", LogHelper.LogType.Event); + + // 创建IPC文件 + string tempDir = Path.GetTempPath(); + string ipcFileName = IpcFilePrefix + Guid.NewGuid().ToString("N") + ".tmp"; + string ipcFilePath = Path.Combine(tempDir, ipcFileName); + + // 写入文件路径到IPC文件 + File.WriteAllText(ipcFilePath, filePath, Encoding.UTF8); + + // 创建事件通知已运行实例 + using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName)) + { + ipcEvent.Set(); + } + + // 等待一段时间让已运行实例处理文件 + Thread.Sleep(1000); + + // 清理IPC文件 + try + { + if (File.Exists(ipcFilePath)) + { + File.Delete(ipcFilePath); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning); + } + + LogHelper.WriteLogToFile("IPC文件路径发送完成", LogHelper.LogType.Event); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"通过IPC发送文件路径失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 启动IPC监听器,等待其他实例发送文件路径 + /// + public static void StartIpcListener() + { + try + { + Thread ipcThread = new Thread(() => + { + try + { + LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event); + + using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName)) + { + while (true) + { + // 等待IPC事件 + if (ipcEvent.WaitOne(IpcTimeout)) + { + // 处理IPC文件 + ProcessIpcFiles(); + + // 重置事件 + ipcEvent.Reset(); + } + + // 检查应用是否还在运行 + if (Application.Current == null || Application.Current.Dispatcher == null) + { + break; + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error); + } + }); + + ipcThread.IsBackground = true; + ipcThread.Start(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动IPC监听器失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 处理IPC文件 + /// + private static void ProcessIpcFiles() + { + try + { + string tempDir = Path.GetTempPath(); + string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp"); + + foreach (string ipcFile in ipcFiles) + { + try + { + // 读取文件路径 + string filePath = File.ReadAllText(ipcFile, Encoding.UTF8); + + if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) + { + LogHelper.WriteLogToFile($"IPC接收到文件路径: {filePath}", LogHelper.LogType.Event); + + // 在UI线程中处理文件打开 + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + try + { + // 获取主窗口并打开文件 + if (Application.Current.MainWindow is MainWindow mainWindow) + { + mainWindow.OpenSingleStrokeFile(filePath); + mainWindow.ShowNotification($"已加载墨迹文件: {Path.GetFileName(filePath)}"); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"IPC处理文件打开失败: {ex.Message}", LogHelper.LogType.Error); + } + })); + } + + // 删除IPC文件 + File.Delete(ipcFile); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning); + + // 尝试删除损坏的IPC文件 + try + { + if (File.Exists(ipcFile)) + { + File.Delete(ipcFile); + } + } + catch { } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理IPC文件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + [DllImport("shell32.dll")] + private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2); + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index c3ace02e..e3185c6f 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -523,6 +523,9 @@ namespace Ink_Canvas // 初始化墨迹渐隐管理器 InitializeInkFadeManager(); + + // 处理命令行参数中的文件路径 + HandleCommandLineFileOpen(); } private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e) @@ -2126,6 +2129,43 @@ namespace Ink_Canvas } #endregion + /// + /// 处理命令行参数中的文件路径 + /// + private void HandleCommandLineFileOpen() + { + try + { + // 检查启动参数中是否有.icstk文件 + string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(App.StartArgs); + + if (!string.IsNullOrEmpty(icstkFile)) + { + LogHelper.WriteLogToFile($"检测到命令行参数中的.icstk文件: {icstkFile}", LogHelper.LogType.Event); + + // 延迟执行,确保UI已完全加载 + Dispatcher.BeginInvoke(new Action(() => + { + try + { + // 打开文件 + OpenSingleStrokeFile(icstkFile); + ShowNotification($"已加载墨迹文件: {Path.GetFileName(icstkFile)}"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"打开命令行参数中的文件失败: {ex.Message}", LogHelper.LogType.Error); + ShowNotification("打开墨迹文件失败"); + } + }), DispatcherPriority.Loaded); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理命令行文件打开时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + /// /// 集中管理工具模式切换和快捷键状态更新 /// 避免在每个工具按钮点击时重复刷新快捷键状态 diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs index fb242d97..b2b0d2a6 100644 --- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs +++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs @@ -643,7 +643,7 @@ namespace Ink_Canvas /// /// 打开单个墨迹文件 /// - private void OpenSingleStrokeFile(string filePath) + public void OpenSingleStrokeFile(string filePath) { var fileStreamHasNoStroke = false; using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))