diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index b662a247..3f9f8c90 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -1,3 +1,8 @@ +using Hardcodet.Wpf.TaskbarNotification; +using Ink_Canvas.Helpers; +using iNKORE.UI.WPF.Modern.Controls; +using Microsoft.Win32; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,11 +18,6 @@ using System.Windows; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Threading; -using Hardcodet.Wpf.TaskbarNotification; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; -using Microsoft.Win32; -using Newtonsoft.Json; using Application = System.Windows.Application; using MessageBox = System.Windows.MessageBox; using Timer = System.Threading.Timer; @@ -80,7 +80,7 @@ namespace Ink_Canvas args = Environment.GetCommandLineArgs(); bool isUpdateMode = args.Contains("--update-mode"); bool isFinalApp = args.Contains("--final-app"); - + if (CrashAction == CrashActionType.SilentRestart && !isUpdateMode && !isFinalApp) { StartWatchdogIfNeeded(); @@ -465,16 +465,16 @@ namespace Ink_Canvas { // 初始化应用启动时间 appStartTime = DateTime.Now; - + /*if (!StoreHelper.IsStoreApp) */ RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version)); - + // 检查是否为最终应用启动(更新后的应用) bool isFinalApp = e.Args.Contains("--final-app"); bool skipMutexCheck = e.Args.Contains("--skip-mutex-check"); - + // 记录最终应用启动状态 if (isFinalApp) { @@ -499,7 +499,7 @@ namespace Ink_Canvas // 处理更新模式启动 bool isUpdateMode = AutoUpdateHelper.HandleUpdateModeStartup(e.Args); - + // 如果是更新模式,不显示主窗口但保持应用运行 if (isUpdateMode) { @@ -510,10 +510,10 @@ namespace Ink_Canvas // 检查是否存在更新标记文件 string updateMarkerFile = Path.Combine(RootPath, "update_in_progress.tmp"); bool isUpdateInProgress = false; - + // 检查是否以更新模式启动 isUpdateMode = e.Args.Contains("--update-mode"); - + // 如果是最终应用启动,立即清理更新标记文件 if (isFinalApp) { @@ -530,7 +530,7 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning); } } - + // 如果不是最终应用启动,才检查更新标记文件 if (!isFinalApp && File.Exists(updateMarkerFile)) { @@ -540,7 +540,7 @@ namespace Ink_Canvas if (int.TryParse(updateProcessIdStr, out int updateProcessId)) { LogHelper.WriteLogToFile($"App | 检测到更新标记文件,更新进程ID: {updateProcessId}"); - + // 检查更新进程是否还在运行 try { @@ -549,18 +549,18 @@ namespace Ink_Canvas { LogHelper.WriteLogToFile("App | 更新进程仍在运行,等待更新完成"); isUpdateInProgress = true; - + // 等待更新进程完成 int waitCount = 0; const int maxWaitCount = 10; // 减少等待时间到10秒 - + while (waitCount < maxWaitCount && !updateProcess.HasExited) { Thread.Sleep(500); // 减少等待间隔到500ms waitCount++; LogHelper.WriteLogToFile($"App | 等待更新进程完成... ({waitCount}/{maxWaitCount})"); } - + if (updateProcess.HasExited) { LogHelper.WriteLogToFile("App | 更新进程已结束"); @@ -592,7 +592,7 @@ namespace Ink_Canvas { LogHelper.WriteLogToFile("App | 更新进程已不存在"); } - + // 无论更新进程是否还在运行,都清理标记文件 try { @@ -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; // 多开时标记为用户主动退出 // 写入退出信号,确保看门狗不会重启 @@ -668,11 +689,11 @@ namespace Ink_Canvas { LogHelper.WriteLogToFile("App | 更新过程中,跳过重复运行检测"); } - + // 在特殊模式下,创建一个临时的Mutex以避免其他检查出错 string mutexName = isFinalApp ? "InkCanvasForClass CE Final" : "InkCanvasForClass CE Update"; mutex = new Mutex(true, mutexName, out bool tempRet); - + // 额外等待一小段时间确保更新进程完全退出 Thread.Sleep(1000); LogHelper.WriteLogToFile("App | 特殊模式等待完成,继续启动"); @@ -681,12 +702,35 @@ namespace Ink_Canvas _taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon"); StartArgs = e.Args; - + // 在非更新模式下创建主窗口 var mainWindow = new MainWindow(); 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/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 4b4d635f..7e0bc215 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -49,5 +49,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.9.0")] -[assembly: AssemblyFileVersion("1.7.9.0")] +[assembly: AssemblyVersion("1.7.9.1")] +[assembly: AssemblyFileVersion("1.7.9.1")] diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs index 0948fa44..2c61c0cf 100644 --- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -128,7 +128,7 @@ namespace Ink_Canvas.Helpers if (points.Length < 4) return points; var result = new List(); - + // 添加第一个点 result.Add(points[0]); @@ -142,9 +142,9 @@ namespace Ink_Canvas.Helpers // 计算改进的控制点 var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3); - + // 限制插值步数,避免点数爆炸 - int steps = Math.Min(UseAdaptiveInterpolation ? + int steps = Math.Min(UseAdaptiveInterpolation ? CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps, 16); // 生成贝塞尔曲线点,但跳过第一个点避免重复 @@ -179,7 +179,7 @@ namespace Ink_Canvas.Helpers // 计算控制点距离(基于点间距离) double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y)); double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y)); - + double controlDist1 = dist1 * CurveTension; double controlDist2 = dist2 * CurveTension; @@ -214,7 +214,7 @@ namespace Ink_Canvas.Helpers // 基于长度和曲率计算步数 int baseSteps = Math.Max(8, Math.Min(20, (int)(totalLength / 10))); int curvatureSteps = (int)(curvature * 10); - + return Math.Max(InterpolationSteps, Math.Min(24, baseSteps + curvatureSteps)); } diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/AutoUpdateHelper.cs index 397a381b..d860f4b8 100644 --- a/Ink Canvas/Helpers/AutoUpdateHelper.cs +++ b/Ink Canvas/Helpers/AutoUpdateHelper.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -15,8 +17,6 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Ink_Canvas.Helpers { @@ -1238,7 +1238,7 @@ namespace Ink_Canvas.Helpers // 查找解压后的主程序文件 string newAppPath = null; string[] possibleExeNames = { "InkCanvasForClass.exe", "Ink Canvas.exe", "InkCanvas.exe" }; - + foreach (string exeName in possibleExeNames) { string testPath = Path.Combine(extractPath, exeName); @@ -1260,12 +1260,12 @@ namespace Ink_Canvas.Helpers try { LogHelper.WriteLogToFile($"AutoUpdate | 准备启动新版本进程: {newAppPath}"); - + // 启动新版本进程(以更新模式) string arguments = $"--update-mode --old-process-id={currentProcessId} --extract-path=\"{extractPath}\" --target-path=\"{currentAppDir}\" --is-silence={isInSilence}"; - + LogHelper.WriteLogToFile($"AutoUpdate | 启动新进程的命令行: {newAppPath} {arguments}"); - + ProcessStartInfo startInfo = new ProcessStartInfo { FileName = newAppPath, @@ -1276,10 +1276,10 @@ namespace Ink_Canvas.Helpers Process.Start(startInfo); LogHelper.WriteLogToFile("AutoUpdate | 新版本进程启动命令已执行"); - + // 等待一小段时间确保新进程启动 Thread.Sleep(2000); - + // 关闭当前旧软件进程 LogHelper.WriteLogToFile("AutoUpdate | 关闭当前旧软件进程"); App.IsAppExitByUser = true; @@ -1312,7 +1312,7 @@ namespace Ink_Canvas.Helpers if (args.Contains("--update-mode")) { LogHelper.WriteLogToFile("AutoUpdate | 检测到更新模式启动"); - + // 解析命令行参数 int oldProcessId = -1; string extractPath = null; @@ -1326,7 +1326,7 @@ namespace Ink_Canvas.Helpers { string arg = args[i]; LogHelper.WriteLogToFile($"AutoUpdate | 处理参数 {i}: {arg}"); - + if (arg.StartsWith("--old-process-id=")) { string processIdStr = arg.Substring("--old-process-id=".Length); @@ -1365,7 +1365,7 @@ namespace Ink_Canvas.Helpers // 启动更新任务 Task.Run(async () => await PerformUpdate(oldProcessId, extractPath, targetPath, isSilence)); return true; // 返回true表示是更新模式 - } + } LogHelper.WriteLogToFile($"AutoUpdate | 参数验证失败 - 老进程ID: {oldProcessId}, 解压路径: {extractPath}, 目标路径: {targetPath}", LogHelper.LogType.Error); return false; @@ -1437,7 +1437,7 @@ namespace Ink_Canvas.Helpers // 复制文件到目标目录 LogHelper.WriteLogToFile($"AutoUpdate | 开始复制文件从 {extractPath} 到 {targetPath}"); - + try { // 使用递归复制方法,支持重试机制 @@ -1445,11 +1445,11 @@ namespace Ink_Canvas.Helpers if (copySuccess) { LogHelper.WriteLogToFile("AutoUpdate | 文件复制完成"); - } - else - { + } + else + { LogHelper.WriteLogToFile("AutoUpdate | 文件复制失败,部分文件可能无法覆盖", LogHelper.LogType.Error); - + if (!isSilence) { MessageBox.Show("更新失败:部分文件无法覆盖,可能是文件正在使用中。\n请关闭所有相关程序后重试。", "更新失败", MessageBoxButton.OK, MessageBoxImage.Error); @@ -1460,7 +1460,7 @@ namespace Ink_Canvas.Helpers catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | 文件复制失败: {ex.Message}", LogHelper.LogType.Error); - + if (!isSilence) { MessageBox.Show($"更新失败:文件复制时出错\n{ex.Message}", "更新失败", MessageBoxButton.OK, MessageBoxImage.Error); @@ -1472,7 +1472,7 @@ namespace Ink_Canvas.Helpers try { LogHelper.WriteLogToFile("AutoUpdate | 清理临时文件"); - + // 删除解压目录 if (Directory.Exists(extractPath)) { @@ -1503,23 +1503,23 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile("AutoUpdate | 更新操作完成"); - // 启动更新后的应用程序 + // 启动更新后的应用程序 string newAppPath = Path.Combine(targetPath, "InkCanvasForClass.exe"); if (File.Exists(newAppPath)) { try { LogHelper.WriteLogToFile($"AutoUpdate | 准备启动更新后的应用程序: {newAppPath}"); - + // 获取当前更新进程ID int currentUpdateProcessId = Process.GetCurrentProcess().Id; LogHelper.WriteLogToFile($"AutoUpdate | 当前更新进程ID: {currentUpdateProcessId}"); - + // 创建一个临时标记文件,用于新进程检测更新状态 string updateMarkerFile = Path.Combine(targetPath, "update_in_progress.tmp"); File.WriteAllText(updateMarkerFile, currentUpdateProcessId.ToString()); LogHelper.WriteLogToFile($"AutoUpdate | 创建更新标记文件: {updateMarkerFile}"); - + // 启动更新后的应用程序(标记为最终应用,不受相同进程影响) ProcessStartInfo startInfo = new ProcessStartInfo { @@ -1528,24 +1528,24 @@ namespace Ink_Canvas.Helpers WorkingDirectory = targetPath, UseShellExecute = false }; - + Process newProcess = Process.Start(startInfo); LogHelper.WriteLogToFile($"AutoUpdate | 最终应用程序启动成功,PID: {newProcess?.Id},已标记为最终应用"); - + // 等待一小段时间确保最终应用程序启动 Thread.Sleep(2000); - + // 结束当前更新进程 LogHelper.WriteLogToFile("AutoUpdate | 更新流程完成,结束更新进程"); - + // 强制结束当前更新进程 try { LogHelper.WriteLogToFile("AutoUpdate | 强制结束更新进程"); - + // 标记为应用主动退出,避免看门狗重启 App.IsAppExitByUser = true; - + // 写入退出信号文件,确保看门狗不会重启 try { @@ -1557,7 +1557,7 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"AutoUpdate | 写入看门狗退出信号文件失败: {ex.Message}", LogHelper.LogType.Warning); } - + Environment.Exit(0); } catch (Exception ex) @@ -1569,7 +1569,7 @@ namespace Ink_Canvas.Helpers catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | 启动更新后的应用程序失败: {ex.Message}", LogHelper.LogType.Error); - + if (!isSilence) { MessageBox.Show($"更新完成,但启动应用程序失败:{ex.Message}\n请手动启动应用程序。", "启动失败", MessageBoxButton.OK, MessageBoxImage.Warning); @@ -1579,7 +1579,7 @@ namespace Ink_Canvas.Helpers else { LogHelper.WriteLogToFile($"AutoUpdate | 更新后的应用程序文件不存在: {newAppPath}", LogHelper.LogType.Error); - + if (!isSilence) { MessageBox.Show($"更新完成,但未找到应用程序文件:{newAppPath}\n请检查更新是否成功。", "文件缺失", MessageBoxButton.OK, MessageBoxImage.Error); @@ -1589,7 +1589,7 @@ namespace Ink_Canvas.Helpers catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | 执行更新操作时出错: {ex.Message}", LogHelper.LogType.Error); - + if (!isSilence) { MessageBox.Show($"更新失败:{ex.Message}", "更新失败", MessageBoxButton.OK, MessageBoxImage.Error); @@ -1615,7 +1615,7 @@ namespace Ink_Canvas.Helpers { string targetFilePath = Path.Combine(destinationDir, file.Name); bool fileCopied = false; - + // 重试机制,最多重试3次 for (int retry = 0; retry < 3; retry++) { @@ -1638,7 +1638,7 @@ namespace Ink_Canvas.Helpers } } } - + await Task.Run(() => file.CopyTo(targetFilePath)); fileCopied = true; break; @@ -1646,14 +1646,14 @@ namespace Ink_Canvas.Helpers catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | 复制文件失败 (重试 {retry + 1}/3) {file.FullName} -> {targetFilePath}: {ex.Message}", LogHelper.LogType.Warning); - + if (retry < 2) { Thread.Sleep(1000); // 等待1秒后重试 } } } - + if (!fileCopied) { allFilesCopied = false; @@ -1698,7 +1698,7 @@ namespace Ink_Canvas.Helpers { File.Delete(targetFilePath); } - + await Task.Run(() => file.CopyTo(targetFilePath)); } catch (Exception ex) @@ -1866,7 +1866,7 @@ namespace Ink_Canvas.Helpers } // 执行安装,静默模式 - InstallNewVersionApp(remoteVersion, true); + InstallNewVersionApp(remoteVersion, true); App.IsAppExitByUser = true; Application.Current.Dispatcher.Invoke(() => { @@ -1996,7 +1996,7 @@ namespace Ink_Canvas.Helpers return false; } LogHelper.WriteLogToFile($"AutoUpdate | 手动安装版本: {version}"); - InstallNewVersionApp(version, true); + InstallNewVersionApp(version, true); App.IsAppExitByUser = true; Application.Current.Dispatcher.Invoke(() => { diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs index f67b5387..c079bc98 100644 --- a/Ink Canvas/Helpers/DeviceIdentifier.cs +++ b/Ink Canvas/Helpers/DeviceIdentifier.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -5,7 +6,6 @@ using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; -using Newtonsoft.Json; namespace Ink_Canvas.Helpers { @@ -758,26 +758,26 @@ namespace Ink_Canvas.Helpers // 如果所有文件都不存在或损坏,返回新的统计对象 - var newStats = new UsageStats - { - DeviceId = DeviceId, - LastLaunchTime = DateTime.Now, - LaunchCount = 0, - TotalUsageSeconds = 0, - AverageSessionSeconds = 0, - LastUpdateCheck = DateTime.MinValue, - UpdatePriority = UpdatePriority.Medium, - UsageFrequency = UsageFrequency.Medium - }; + var newStats = new UsageStats + { + DeviceId = DeviceId, + LastLaunchTime = DateTime.Now, + LaunchCount = 0, + TotalUsageSeconds = 0, + AverageSessionSeconds = 0, + LastUpdateCheck = DateTime.MinValue, + UpdatePriority = UpdatePriority.Medium, + UsageFrequency = UsageFrequency.Medium + }; // 保存新统计到文件 SaveUsageStatsToFile(UsageStatsFilePath, newStats); - return newStats; + return newStats; } catch (Exception ex) { LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error); - + // 返回默认统计对象 return new UsageStats { @@ -800,7 +800,7 @@ namespace Ink_Canvas.Helpers { // 保存到主文件 SaveUsageStatsToFile(UsageStatsFilePath, stats); - + // 保存到备份文件 SaveUsageStatsToFile(UsageStatsBackupPath, stats); } @@ -822,7 +822,7 @@ namespace Ink_Canvas.Helpers { return stats; } - + } } @@ -843,31 +843,31 @@ namespace Ink_Canvas.Helpers if (File.Exists(filePath)) { byte[] encryptedData = File.ReadAllBytes(filePath); - + if (encryptedData.Length < 32) // SHA256校验和长度为32字节 { LogHelper.WriteLogToFile($"DeviceIdentifier | 加密文件格式错误: {filePath}", LogHelper.LogType.Error); return null; } - + // 提取校验和和加密数据 byte[] checksum = new byte[32]; byte[] data = new byte[encryptedData.Length - 32]; Array.Copy(encryptedData, 0, checksum, 0, 32); Array.Copy(encryptedData, 32, data, 0, data.Length); - + // 使用SHA256生成解密密钥 using (var sha256 = SHA256.Create()) { byte[] keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(DeviceId + "ICC_Usage_Stats_Salt")); - + // XOR解密 byte[] decryptedData = new byte[data.Length]; for (int i = 0; i < data.Length; i++) { decryptedData[i] = (byte)(data[i] ^ keyBytes[i % keyBytes.Length]); } - + // 验证校验和 byte[] computedChecksum = sha256.ComputeHash(decryptedData); if (!checksum.SequenceEqual(computedChecksum)) @@ -875,7 +875,7 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"DeviceIdentifier | 加密文件校验和验证失败: {filePath}", LogHelper.LogType.Error); return null; } - + string json = Encoding.UTF8.GetString(decryptedData); var stats = JsonConvert.DeserializeObject(json); if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) @@ -909,27 +909,27 @@ namespace Ink_Canvas.Helpers string json = JsonConvert.SerializeObject(stats, Formatting.Indented); byte[] data = Encoding.UTF8.GetBytes(json); - + // 使用SHA256生成加密密钥(基于设备ID) using (var sha256 = SHA256.Create()) { byte[] keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(DeviceId + "ICC_Usage_Stats_Salt")); - + // 简单的XOR加密 byte[] encryptedData = new byte[data.Length]; for (int i = 0; i < data.Length; i++) { encryptedData[i] = (byte)(data[i] ^ keyBytes[i % keyBytes.Length]); } - + // 添加SHA256校验和 byte[] checksum = sha256.ComputeHash(data); byte[] finalData = new byte[checksum.Length + encryptedData.Length]; checksum.CopyTo(finalData, 0); encryptedData.CopyTo(finalData, checksum.Length); - + File.WriteAllBytes(filePath, finalData); - + LogHelper.WriteLogToFile($"DeviceIdentifier | 加密使用统计已保存到: {filePath}"); } } @@ -958,7 +958,7 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"DeviceIdentifier | 记录更新检查失败: {ex.Message}", LogHelper.LogType.Error); } } - + /// /// 从备份文件恢复使用统计数据 @@ -997,7 +997,7 @@ namespace Ink_Canvas.Helpers try { var status = new List(); - + // 检查主文件 if (File.Exists(UsageStatsFilePath)) { @@ -1339,7 +1339,7 @@ namespace Ink_Canvas.Helpers return descriptions.Count > 0 ? string.Join(", ", descriptions) : "普通用户"; } - + /// /// 关机时保存使用时间数据 /// @@ -1360,7 +1360,7 @@ namespace Ink_Canvas.Helpers // 2. 计算本次会话时长(防止异常值) TimeSpan sessionDuration = DateTime.Now - App.appStartTime; long sessionSeconds = Math.Max(0, (long)sessionDuration.TotalSeconds); - + // 防止异常大的会话时长(超过24小时) if (sessionSeconds > 86400) { @@ -1373,10 +1373,10 @@ namespace Ink_Canvas.Helpers stats.LaunchCount++; stats.AverageSessionSeconds = stats.TotalUsageSeconds / (double)Math.Max(1, stats.LaunchCount); stats.LastLaunchTime = DateTime.Now; - + // 4. 保存数据 SaveUsageStats(stats); - + LogHelper.WriteLogToFile("DeviceIdentifier | 关机保存完成"); } catch (Exception ex) 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/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs index b1f1eefe..b5ba3f40 100644 --- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs +++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs @@ -1,11 +1,11 @@ +using Newtonsoft.Json; +using NHotkey.Wpf; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using System.Windows.Input; -using Newtonsoft.Json; -using NHotkey.Wpf; namespace Ink_Canvas.Helpers { @@ -19,7 +19,7 @@ namespace Ink_Canvas.Helpers private readonly MainWindow _mainWindow; private bool _isDisposed; private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键 - + // 配置文件路径 private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "HotkeyConfig.json"); #endregion @@ -227,7 +227,7 @@ namespace Ink_Canvas.Helpers try { // 开始注册默认快捷键集合 - + // 基本操作快捷键 RegisterHotkey("Undo", Key.Z, ModifierKeys.Control, () => _mainWindow.SymbolIconUndo_MouseUp(null, null)); RegisterHotkey("Redo", Key.Y, ModifierKeys.Control, () => _mainWindow.SymbolIconRedo_MouseUp(null, null)); @@ -272,14 +272,14 @@ namespace Ink_Canvas.Helpers try { // 开始从配置文件加载快捷键设置 - + // 检查是否应该注册快捷键 if (!_hotkeysShouldBeRegistered) { // 当前状态不允许注册快捷键,跳过加载 return; } - + // 尝试从配置文件加载 if (LoadHotkeysFromConfigFile()) { @@ -319,7 +319,7 @@ namespace Ink_Canvas.Helpers try { LogHelper.WriteLogToFile("开始保存快捷键配置到配置文件", LogHelper.LogType.Event); - + if (SaveHotkeysToConfigFile()) { LogHelper.WriteLogToFile("快捷键配置已成功保存到配置文件", LogHelper.LogType.Event); @@ -347,7 +347,7 @@ namespace Ink_Canvas.Helpers { _hotkeysShouldBeRegistered = true; LogHelper.WriteLogToFile("启用快捷键注册功能"); - + // 立即加载快捷键设置 LoadHotkeysFromSettings(); } @@ -376,7 +376,7 @@ namespace Ink_Canvas.Helpers { _hotkeysShouldBeRegistered = false; LogHelper.WriteLogToFile("禁用快捷键注册功能"); - + // 注销所有快捷键 UnregisterAllHotkeys(); } @@ -438,20 +438,20 @@ namespace Ink_Canvas.Helpers // 获取原有的动作 var originalAction = _registeredHotkeys[hotkeyName].Action; - + // 注销原有快捷键 UnregisterHotkey(hotkeyName); - + // 注册新的快捷键 var success = RegisterHotkey(hotkeyName, key, modifiers, originalAction); - + if (success) { LogHelper.WriteLogToFile($"成功更新快捷键 {hotkeyName}: {modifiers}+{key}", LogHelper.LogType.Event); // 自动保存配置 SaveHotkeysToSettings(); } - + return success; } catch (Exception ex) @@ -472,17 +472,17 @@ namespace Ink_Canvas.Helpers try { // 通过反射访问主窗口的penType字段 - var penTypeField = _mainWindow.GetType().GetField("penType", + var penTypeField = _mainWindow.GetType().GetField("penType", BindingFlags.NonPublic | BindingFlags.Instance); - + if (penTypeField != null) { penTypeField.SetValue(_mainWindow, penTypeIndex); - + // 调用CheckPenTypeUIState方法更新UI状态 - var checkPenTypeMethod = _mainWindow.GetType().GetMethod("CheckPenTypeUIState", + var checkPenTypeMethod = _mainWindow.GetType().GetMethod("CheckPenTypeUIState", BindingFlags.NonPublic | BindingFlags.Instance); - + if (checkPenTypeMethod != null) { checkPenTypeMethod.Invoke(_mainWindow, null); @@ -604,7 +604,7 @@ namespace Ink_Canvas.Helpers { Formatting = Formatting.Indented }; - + string jsonContent = JsonConvert.SerializeObject(config, settings); // 直接写入原文件,覆盖原有内容 @@ -688,9 +688,9 @@ namespace Ink_Canvas.Helpers try { // 通过反射访问主窗口的FloatingbarSelectionBG字段 - var floatingbarSelectionBGField = _mainWindow.GetType().GetField("FloatingbarSelectionBG", + var floatingbarSelectionBGField = _mainWindow.GetType().GetField("FloatingbarSelectionBG", BindingFlags.NonPublic | BindingFlags.Instance); - + if (floatingbarSelectionBGField != null) { var floatingbarSelectionBG = floatingbarSelectionBGField.GetValue(_mainWindow); @@ -707,7 +707,7 @@ namespace Ink_Canvas.Helpers return true; // 返回true表示应该注销快捷键 } } - + // 通过反射访问Canvas.GetLeft方法来获取高光位置 var canvasType = Type.GetType("System.Windows.Controls.Canvas, PresentationFramework"); if (canvasType != null) @@ -719,7 +719,7 @@ namespace Ink_Canvas.Helpers if (leftPosition != null) { var position = Convert.ToDouble(leftPosition); - + // 根据高光位置判断当前选中的工具 // 位置计算基于SetFloatingBarHighlightPosition方法中的逻辑 bool isMouseMode; @@ -739,20 +739,20 @@ namespace Ink_Canvas.Helpers { isMouseMode = false; } - + return isMouseMode; } } } } } - + // 如果无法获取高光状态,则回退到inkCanvas.EditingMode判断 - + // 通过反射访问主窗口的inkCanvas字段 - var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas", + var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas", BindingFlags.NonPublic | BindingFlags.Instance); - + if (inkCanvasField != null) { var inkCanvas = inkCanvasField.GetValue(_mainWindow); @@ -768,23 +768,23 @@ namespace Ink_Canvas.Helpers // 检查是否为批注模式 var isInkMode = editingMode.ToString().Contains("Ink"); var isSelectMode = editingMode.ToString().Contains("Select"); - + // 如果是批注模式或选择模式,则应该注册快捷键(返回false) // 如果是橡皮擦模式或其他模式,则不应该注册快捷键(返回true) var shouldNotRegisterHotkeys = !isInkMode && !isSelectMode; - + return shouldNotRegisterHotkeys; } } } } - + // 如果无法获取任何状态信息,则回退到原来的判断逻辑 - + // 通过反射访问主窗口的currentMode字段(作为最后的备用方案) - var currentModeField = _mainWindow.GetType().GetField("currentMode", + var currentModeField = _mainWindow.GetType().GetField("currentMode", BindingFlags.NonPublic | BindingFlags.Instance); - + if (currentModeField != null) { var currentMode = currentModeField.GetValue(_mainWindow); @@ -798,7 +798,7 @@ namespace Ink_Canvas.Helpers return isSelectMode; } } - + return false; // 默认允许快捷键 } catch (Exception ex) @@ -816,7 +816,7 @@ namespace Ink_Canvas.Helpers { if (!_isDisposed) { - + _isDisposed = true; } } @@ -861,4 +861,4 @@ namespace Ink_Canvas.Helpers } #endregion } -} +} diff --git a/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs b/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs index f249885d..0e4143d3 100644 --- a/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs +++ b/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs @@ -28,13 +28,13 @@ namespace Ink_Canvas.Helpers return originalStroke; var originalPoints = originalStroke.StylusPoints.ToArray(); - + // 预处理:去除噪声点 var cleanedPoints = RemoveNoisePoints(originalPoints); - + // 使用改进的贝塞尔曲线拟合 var smoothedPoints = ApplyCubicBezierSmoothing(cleanedPoints); - + // 后处理:重采样和优化 var finalPoints = PostProcessPoints(smoothedPoints); @@ -61,7 +61,7 @@ namespace Ink_Canvas.Helpers var next = points[i + 1]; // 计算到前一个点的距离 - double distToPrev = Math.Sqrt((curr.X - prev.X) * (curr.X - prev.X) + + double distToPrev = Math.Sqrt((curr.X - prev.X) * (curr.X - prev.X) + (curr.Y - prev.Y) * (curr.Y - prev.Y)); // 如果距离太近,跳过这个点 @@ -148,7 +148,7 @@ namespace Ink_Canvas.Helpers // 计算控制点距离 double dist1 = CalculateDistance(p0, p1); double dist2 = CalculateDistance(p2, p3); - + double controlDist1 = dist1 * _config.CurveTension; double controlDist2 = dist2 * _config.CurveTension; @@ -322,4 +322,4 @@ namespace Ink_Canvas.Helpers return result.ToArray(); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/InkFadeManager.cs b/Ink Canvas/Helpers/InkFadeManager.cs index 6f8188f8..4490b721 100644 --- a/Ink Canvas/Helpers/InkFadeManager.cs +++ b/Ink Canvas/Helpers/InkFadeManager.cs @@ -63,7 +63,7 @@ namespace Ink_Canvas.Helpers /// 抬笔点 public void AddFadingStroke(Stroke stroke, Point startPoint, Point endPoint) { - if (!IsEnabled || stroke == null) + if (!IsEnabled || stroke == null) { return; } @@ -238,12 +238,12 @@ namespace Ink_Canvas.Helpers public void UpdateFadeTime(int fadeTime) { FadeTime = fadeTime; - + foreach (var kvp in _fadeTimers) { var stroke = kvp.Key; var timer = kvp.Value; - + timer.Stop(); timer.Interval = TimeSpan.FromMilliseconds(FadeTime); timer.Start(); @@ -283,14 +283,14 @@ namespace Ink_Canvas.Helpers { // 创建路径几何,使用墨迹的实际位置 var geometry = stroke.GetGeometry(); - if (geometry == null) + if (geometry == null) { return null; } // 获取绘画属性 var drawingAttribs = stroke.DrawingAttributes; - + // 创建路径元素,确保使用正确的绘画属性 var path = new Path { @@ -302,7 +302,7 @@ namespace Ink_Canvas.Helpers StrokeLineJoin = PenLineJoin.Round, Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null, // 高亮笔需要填充 Opacity = 0.95, // 初始透明度更高,显得更自然 - + // 优化渲染质量 UseLayoutRounding = false, SnapsToDevicePixels = false @@ -312,19 +312,19 @@ namespace Ink_Canvas.Helpers if (drawingAttribs.IsHighlighter) { path.Opacity = 0.4; // 高亮笔初始透明度更低,更符合荧光笔特性 - + // 为高亮笔添加特殊的混合效果 // 使用更柔和的笔触样式 path.StrokeStartLineCap = PenLineCap.Flat; path.StrokeEndLineCap = PenLineCap.Flat; path.StrokeLineJoin = PenLineJoin.Miter; - - // 高亮笔通常需要更宽的笔触来覆盖下面的内容 - if (drawingAttribs.Width < 20) - { - path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20); - } - } + + // 高亮笔通常需要更宽的笔触来覆盖下面的内容 + if (drawingAttribs.Width < 20) + { + path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20); + } + } // 不设置任何变换,保持墨迹原有粗细 var bounds = geometry.Bounds; @@ -356,7 +356,7 @@ namespace Ink_Canvas.Helpers // 获取当前透明度和判断是否为高亮笔 var currentOpacity = visual.Opacity; var isHighlighter = stroke.DrawingAttributes.IsHighlighter; - + // 根据墨迹类型选择不同的动画效果 if (isHighlighter) { @@ -439,19 +439,19 @@ namespace Ink_Canvas.Helpers { var stylusPoints = stroke.StylusPoints; var totalPoints = stylusPoints.Count; - + // 分段算法 - 确保所有墨迹都有足够的动画效果 var strokeLength = CalculateStrokeLength(stylusPoints); var segmentCount = CalculateOptimalSegmentCount(totalPoints, strokeLength); - + // 强制最小分段数量,确保短墨迹也有动画效果 segmentCount = Math.Max(segmentCount, 4); - + var pointsPerSegment = Math.Max(1, totalPoints / segmentCount); // 隐藏原始视觉元素 originalVisual.Visibility = Visibility.Hidden; - + var segments = new List(); var parent = _mainWindow.inkCanvas?.Parent as Panel; if (parent == null) @@ -465,7 +465,7 @@ namespace Ink_Canvas.Helpers { var startIndex = i * pointsPerSegment; var endIndex = (i == segmentCount - 1) ? totalPoints - 1 : (i + 1) * pointsPerSegment; - + // 确保有足够的点来创建分段,对于短墨迹特殊处理 if (endIndex <= startIndex && totalPoints > 1) { @@ -473,12 +473,12 @@ namespace Ink_Canvas.Helpers startIndex = i; endIndex = Math.Min(i + 1, totalPoints - 1); } - + // 为每个分段添加重叠,确保连接处平滑 var overlap = Math.Max(1, pointsPerSegment / 6); // 15%的重叠,平衡平滑与速度 var actualStartIndex = Math.Max(0, startIndex - overlap); var actualEndIndex = Math.Min(totalPoints - 1, endIndex + overlap); - + var segment = CreateStrokeSegment(stroke, actualStartIndex, actualEndIndex, opacity); if (segment != null) { @@ -576,10 +576,10 @@ namespace Ink_Canvas.Helpers for (int i = 0; i < segments.Count; i++) { var segment = segments[i]; - + // 使用预计算的动画曲线获取延迟时间 var delay = animationCurve[i]; - + // 使用定时器延迟启动每个分段的动画 var timer = new DispatcherTimer { @@ -595,7 +595,7 @@ namespace Ink_Canvas.Helpers lock (completedSegments) { completedSegments.Add(segment); - + // 检查是否所有分段都完成了 if (completedSegments.Count >= totalSegments) { @@ -676,7 +676,7 @@ namespace Ink_Canvas.Helpers { // 移除所有分段 var parent = _mainWindow.inkCanvas?.Parent as Panel; - + foreach (var segment in segments) { if (parent != null && parent.Children.Contains(segment)) @@ -729,7 +729,7 @@ namespace Ink_Canvas.Helpers private double CalculateStrokeLength(StylusPointCollection points) { if (points.Count < 2) return 0; - + double totalLength = 0; for (int i = 1; i < points.Count; i++) { @@ -749,22 +749,22 @@ namespace Ink_Canvas.Helpers const double PIXELS_PER_SEGMENT = 12.0; // 每段适中长度,平衡效果与速度 const int MIN_SEGMENTS = 5; // 适当的最小分段数,确保动画效果 const int MAX_SEGMENTS = 100; // 适中的最大分段数,平衡性能与效果 - + // 根据长度计算基础分段数 var lengthBasedSegments = Math.Max(MIN_SEGMENTS, (int)(strokeLength / PIXELS_PER_SEGMENT)); - + // 根据点密度调整,平衡效果与速度 var density = pointCount > 0 ? strokeLength / pointCount : 1; var densityFactor = Math.Max(0.4, Math.Min(2.5, density / 1.8)); - + var finalSegments = (int)(lengthBasedSegments * densityFactor); - + // 对于短墨迹,确保至少有4个分段 if (pointCount <= 5) { finalSegments = Math.Max(finalSegments, 4); } - + // 限制在合理范围内 return Math.Min(MAX_SEGMENTS, Math.Max(MIN_SEGMENTS, finalSegments)); } @@ -778,7 +778,7 @@ namespace Ink_Canvas.Helpers var baseDuration = totalDuration / Math.Max(segmentCount, 1); var minDuration = 150; // 每段最少150ms,确保动画完整显示 var maxDuration = 500; // 每段最多500ms,平衡速度与完整性 - + return Math.Max(minDuration, Math.Min(maxDuration, baseDuration)); } @@ -788,17 +788,17 @@ namespace Ink_Canvas.Helpers private int[] CreateAppleStyleAnimationCurve(int segmentCount, int totalDuration) { var curve = new int[segmentCount]; - + // 平衡速度与完整性,确保动画有足够时间播放 var availableTime = totalDuration * 0.6; // 使用60%的总时间,给动画留足够缓冲 var delayBetweenSegments = Math.Max(60, availableTime / Math.Max(segmentCount, 1)); - + for (int i = 0; i < segmentCount; i++) { // 线性延迟,确保每个分段都有足够时间 curve[i] = (int)(i * delayBetweenSegments); } - + return curve; } @@ -829,4 +829,4 @@ namespace Ink_Canvas.Helpers } #endregion } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/InkSmoothingConfig.cs b/Ink Canvas/Helpers/InkSmoothingConfig.cs index 83533f0e..dea1df70 100644 --- a/Ink Canvas/Helpers/InkSmoothingConfig.cs +++ b/Ink Canvas/Helpers/InkSmoothingConfig.cs @@ -12,22 +12,22 @@ namespace Ink_Canvas.Helpers public double SmoothingStrength { get; set; } = 0.4; public double ResampleInterval { get; set; } = 2.5; public int InterpolationSteps { get; set; } = 12; - + // 贝塞尔曲线参数 public bool UseAdaptiveInterpolation { get; set; } = true; public double CurveTension { get; set; } = 0.3; public double MinCurvatureThreshold { get; set; } = 0.1; public double MaxCurvatureThreshold { get; set; } = 0.8; - + // 性能参数 public bool UseHardwareAcceleration { get; set; } = true; public bool UseAsyncProcessing { get; set; } = true; public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; public int MaxPointsPerStroke { get; set; } = 10000; - + // 质量设置 public SmoothingQuality Quality { get; set; } = SmoothingQuality.Balanced; - + public enum SmoothingQuality { Performance, // 性能优先 @@ -49,7 +49,7 @@ namespace Ink_Canvas.Helpers public static InkSmoothingConfig FromSettings() { var config = new InkSmoothingConfig(); - + try { // 尝试从MainWindow.Settings加载配置(兼容性) @@ -66,7 +66,7 @@ namespace Ink_Canvas.Helpers { Debug.WriteLine($"加载平滑配置失败: {ex.Message}"); } - + return config; } @@ -85,7 +85,7 @@ namespace Ink_Canvas.Helpers CurveTension = 0.2; MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2); break; - + case SmoothingQuality.Balanced: SmoothingStrength = 0.4; ResampleInterval = 2.5; @@ -94,7 +94,7 @@ namespace Ink_Canvas.Helpers CurveTension = 0.3; MaxConcurrentTasks = Environment.ProcessorCount; break; - + case SmoothingQuality.Quality: SmoothingStrength = 0.6; ResampleInterval = 1.5; @@ -120,7 +120,7 @@ namespace Ink_Canvas.Helpers MainWindow.Settings.Canvas.UseHardwareAcceleration = UseHardwareAcceleration; MainWindow.Settings.Canvas.UseAsyncInkSmoothing = UseAsyncProcessing; MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = MaxConcurrentTasks; - + } } catch (Exception ex) @@ -152,4 +152,4 @@ namespace Ink_Canvas.Helpers $"张力: {CurveTension:F2}, 硬件加速: {UseHardwareAcceleration}"; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs index fd43b18f..436f5d71 100644 --- a/Ink Canvas/Helpers/PPTInkManager.cs +++ b/Ink Canvas/Helpers/PPTInkManager.cs @@ -1,9 +1,9 @@ -using System; +using Microsoft.Office.Interop.PowerPoint; +using System; using System.IO; using System.Security.Cryptography; using System.Text; using System.Windows.Ink; -using Microsoft.Office.Interop.PowerPoint; namespace Ink_Canvas.Helpers { @@ -57,11 +57,11 @@ namespace Ink_Canvas.Helpers { // 完全清理之前的墨迹状态 ClearAllStrokes(); - + // 重置墨迹锁定状态 _inkLockUntil = DateTime.MinValue; _lockedSlideIndex = -1; - + // 生成演示文稿唯一标识符 _currentPresentationId = GeneratePresentationId(presentation); @@ -164,7 +164,7 @@ namespace Ink_Canvas.Helpers { // 确定要保存的页面索引 int saveToSlideIndex = _lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex; - + // 确保页面索引有效 if (saveToSlideIndex > 0 && saveToSlideIndex < _memoryStreams.Length) { @@ -179,7 +179,7 @@ namespace Ink_Canvas.Helpers // 加载新页面的墨迹 var newStrokes = LoadSlideStrokes(slideIndex); LogHelper.WriteLogToFile($"已切换到第{slideIndex}页,加载墨迹数量: {newStrokes.Count}", LogHelper.LogType.Trace); - + return newStrokes; } catch (Exception ex) diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index fb57c16f..d8a03bd4 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Office.Interop.PowerPoint; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -7,7 +8,6 @@ using System.Text; using System.Threading; using System.Timers; using System.Windows.Threading; -using Microsoft.Office.Interop.PowerPoint; using Application = System.Windows.Application; using Timer = System.Timers.Timer; @@ -219,7 +219,7 @@ namespace Ink_Canvas.Helpers { _lastSlideShowState = currentSlideShowState; SlideShowStateChanged?.Invoke(currentSlideShowState); - + if (!currentSlideShowState) { LogHelper.WriteLogToFile("检测到PPT放映已结束", LogHelper.LogType.Trace); diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs index 5bf4b636..b022a29b 100644 --- a/Ink Canvas/Helpers/PPTUIManager.cs +++ b/Ink Canvas/Helpers/PPTUIManager.cs @@ -157,7 +157,7 @@ namespace Ink_Canvas.Helpers { // 检查是否应该显示PPT按钮 // 不仅要检查按钮设置,还要确保确实在PPT放映模式下 - bool shouldShowButtons = ShowPPTButton && + bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _mainWindow.PPTManager?.IsInSlideShow == true; diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs index 1e91a7de..54eb84bd 100644 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs @@ -1,9 +1,9 @@ +using iNKORE.UI.WPF.Modern.Controls; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -using iNKORE.UI.WPF.Modern.Controls; namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher { diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs index 2089ca6c..9b6ac63b 100644 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs @@ -1,3 +1,5 @@ +using Microsoft.Win32; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; @@ -8,8 +10,6 @@ using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; -using Microsoft.Win32; -using Newtonsoft.Json; namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher { diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml.cs index 4b6cda6c..6666f10d 100644 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml.cs +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml.cs @@ -1,10 +1,10 @@ +using Ink_Canvas.Windows; +using Microsoft.Win32; using System; using System.ComponentModel; using System.IO; using System.Windows; using System.Windows.Controls; -using Ink_Canvas.Windows; -using Microsoft.Win32; namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher { diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs index 2c504bd0..555c7bf9 100644 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs @@ -1,3 +1,5 @@ +using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -5,8 +7,6 @@ using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Media; -using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher; -using Newtonsoft.Json; namespace Ink_Canvas.Helpers.Plugins.BuiltIn { diff --git a/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs b/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs index 3b62137f..d22ca80e 100644 --- a/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs +++ b/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs @@ -89,4 +89,4 @@ namespace Ink_Canvas.Helpers.Plugins base.Cleanup(); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs b/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs index 6ee5e9d5..312eb10c 100644 --- a/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs +++ b/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs @@ -45,4 +45,4 @@ namespace Ink_Canvas.Helpers.Plugins /// void OnConfigurationChanged(); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IPluginService.cs b/Ink Canvas/Helpers/Plugins/IPluginService.cs index 2df665f5..ff24ec2b 100644 --- a/Ink Canvas/Helpers/Plugins/IPluginService.cs +++ b/Ink Canvas/Helpers/Plugins/IPluginService.cs @@ -554,4 +554,4 @@ namespace Ink_Canvas.Helpers.Plugins /// Error } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs b/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs index be1efddc..9688cd31 100644 --- a/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs +++ b/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs @@ -1,8 +1,8 @@ +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Newtonsoft.Json; namespace Ink_Canvas.Helpers.Plugins { @@ -270,4 +270,4 @@ namespace Ink_Canvas.Helpers.Plugins } } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginManager.cs b/Ink Canvas/Helpers/Plugins/PluginManager.cs index d1a6559f..27b7742e 100644 --- a/Ink Canvas/Helpers/Plugins/PluginManager.cs +++ b/Ink Canvas/Helpers/Plugins/PluginManager.cs @@ -1,3 +1,5 @@ +using Ink_Canvas.Windows; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -8,8 +10,6 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using System.Windows; -using Ink_Canvas.Windows; -using Newtonsoft.Json; using Timer = System.Timers.Timer; namespace Ink_Canvas.Helpers.Plugins diff --git a/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs b/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs index fd1b1bf7..3f7ec460 100644 --- a/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs +++ b/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs @@ -452,4 +452,4 @@ namespace Ink_Canvas.Helpers.Plugins #endregion } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/SoftwareLauncher.cs b/Ink Canvas/Helpers/SoftwareLauncher.cs index 5f2e405d..e3dc7188 100644 --- a/Ink Canvas/Helpers/SoftwareLauncher.cs +++ b/Ink Canvas/Helpers/SoftwareLauncher.cs @@ -1,8 +1,8 @@ -using System; +using Microsoft.Win32; +using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; -using Microsoft.Win32; namespace Ink_Canvas.Helpers { diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 00b1ac61..42bc95ac 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -2659,6 +2659,26 @@ + + + +