diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index f1df45fb..e85a7288 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -26,10 +26,10 @@ jobs: - name: Build the Solution run: | msbuild -t:restore /p:GitFlow="Github Action" - msbuild /p:platform="Any CPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" + msbuild /p:platform="AnyCPU" /p:configuration="Debug" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" - name: Upload to artifact uses: actions/upload-artifact@v4.5.0 with: name: InkCanvasForClass - path: "Ink Canvas/bin/Any CPU/Release/net472/" + path: "Ink Canvas/bin/Debug/net472" diff --git a/Ink Canvas.sln.DotSettings.user b/Ink Canvas.sln.DotSettings.user index 0401f804..21fc1f49 100644 --- a/Ink Canvas.sln.DotSettings.user +++ b/Ink Canvas.sln.DotSettings.user @@ -1,4 +1,6 @@  WARNING C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe - 1114112 \ No newline at end of file + 1114112 + True + True \ No newline at end of file diff --git a/Ink Canvas/App.xaml b/Ink Canvas/App.xaml index 0eecad8a..83167e38 100644 --- a/Ink Canvas/App.xaml +++ b/Ink Canvas/App.xaml @@ -237,7 +237,6 @@ - diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 094e2735..1f1d5c72 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.11.0")] -[assembly: AssemblyFileVersion("1.7.11.0")] +[assembly: AssemblyVersion("1.7.11.4")] +[assembly: AssemblyFileVersion("1.7.11.4")] diff --git a/Ink Canvas/FloatingWindowInterceptorManager.cs b/Ink Canvas/FloatingWindowInterceptorManager.cs index 9eb9f6ea..9abd6a00 100644 --- a/Ink Canvas/FloatingWindowInterceptorManager.cs +++ b/Ink Canvas/FloatingWindowInterceptorManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows; using Ink_Canvas.Helpers; namespace Ink_Canvas @@ -124,6 +123,37 @@ namespace Ink_Canvas { _settings.InterceptRules[ruleName] = enabled; } + + // 获取规则信息以处理父子关系 + var rule = _interceptor.GetInterceptRule(type); + if (rule != null) + { + // 如果是父规则,更新所有子规则的设置 + if (rule.ChildTypes.Count > 0) + { + foreach (var childType in rule.ChildTypes) + { + var childRuleName = childType.ToString(); + if (_settings.InterceptRules.ContainsKey(childRuleName)) + { + _settings.InterceptRules[childRuleName] = enabled; + } + } + } + // 如果是子规则,更新父规则的设置 + else if (rule.ParentType.HasValue) + { + var parentRule = _interceptor.GetInterceptRule(rule.ParentType.Value); + if (parentRule != null) + { + var parentRuleName = rule.ParentType.Value.ToString(); + if (_settings.InterceptRules.ContainsKey(parentRuleName)) + { + _settings.InterceptRules[parentRuleName] = parentRule.IsEnabled; + } + } + } + } } catch (Exception ex) { diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs index 2c61c0cf..51d0588f 100644 --- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -95,18 +95,22 @@ namespace Ink_Canvas.Helpers // 使用改进的贝塞尔曲线拟合 var smoothedPoints = ApplyImprovedBezierSmoothing(originalPoints); + System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 原始点数={originalPoints.Length}, 平滑后点数={smoothedPoints.Length}"); + cancellationToken.ThrowIfCancellationRequested(); - // 严格控制点数,避免产生过多点 - if (smoothedPoints.Length > originalPoints.Length * 2) + // 放宽点数限制 + if (smoothedPoints.Length > originalPoints.Length * 3.0) { + System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 点数过多,进行重采样"); // 如果点数增加太多,进行重采样 smoothedPoints = ResampleEquidistantOptimized(smoothedPoints, ResampleInterval); } - // 最终检查:确保点数不会过多 - if (smoothedPoints.Length > originalPoints.Length * 1.5) + // 进一步放宽最终检查 + if (smoothedPoints.Length > originalPoints.Length * 2.5) { + System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 重采样后点数仍然过多,返回原始笔画"); // 如果仍然太多点,使用原始笔画 return stroke; } @@ -117,6 +121,7 @@ namespace Ink_Canvas.Helpers DrawingAttributes = stroke.DrawingAttributes.Clone() }; + System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 成功创建平滑笔画"); return smoothedStroke; } @@ -125,33 +130,42 @@ namespace Ink_Canvas.Helpers /// private StylusPoint[] ApplyImprovedBezierSmoothing(StylusPoint[] points) { - if (points.Length < 4) return points; + if (points.Length < 6) return points; // 5次贝塞尔需要6个点 var result = new List(); // 添加第一个点 result.Add(points[0]); - // 使用非重叠的窗口进行贝塞尔曲线拟合 - for (int i = 0; i < points.Length - 3; i += 3) // 每次移动3个点,避免重叠 + // 使用5次贝塞尔曲线,每次移动1个点确保连续性 + for (int i = 0; i < points.Length - 5; i++) { var p0 = points[i]; - var p1 = points[Math.Min(i + 1, points.Length - 1)]; - var p2 = points[Math.Min(i + 2, points.Length - 1)]; - var p3 = points[Math.Min(i + 3, points.Length - 1)]; + var p1 = points[i + 1]; + var p2 = points[i + 2]; + var p3 = points[i + 3]; + var p4 = points[i + 4]; + var p5 = points[i + 5]; - // 计算改进的控制点 - var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3); + // 计算5次贝塞尔的控制点 + var controlPoints = CalculateQuinticControlPoints(p0, p1, p2, p3, p4, p5); - // 限制插值步数,避免点数爆炸 - int steps = Math.Min(UseAdaptiveInterpolation ? - CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps, 16); - - // 生成贝塞尔曲线点,但跳过第一个点避免重复 - for (int j = 1; j <= steps; j++) + // 生成插值点 + if (i == 0) { - double t = (double)j / steps; - var bezierPoint = CubicBezierWithControlPoints(controlPoints, t, p0, p3); + // 第一个窗口:生成更多插值点 + for (int j = 1; j <= 4; j++) + { + double t = (double)j / 5; + var bezierPoint = CalculateQuinticBezierPoint(p0, controlPoints, p5, t); + result.Add(bezierPoint); + } + } + else + { + // 后续窗口:只生成最后一个插值点,避免重复 + double t = 4.0 / 5.0; // 只取最后一个插值点 + var bezierPoint = CalculateQuinticBezierPoint(p0, controlPoints, p5, t); result.Add(bezierPoint); } } @@ -159,8 +173,209 @@ namespace Ink_Canvas.Helpers // 添加最后一个点 result.Add(points[points.Length - 1]); - // 去重和优化点数 - return RemoveDuplicatePoints(result.ToArray()); + System.Diagnostics.Debug.WriteLine($"ApplyImprovedBezierSmoothing: 原始点数={points.Length}, 生成点数={result.Count}"); + + // 使用更宽松的去重 + return RemoveDuplicatePointsLoose(result.ToArray()); + } + + /// + /// 5次贝塞尔曲线控制点计算 + /// + private (Point cp1, Point cp2, Point cp3, Point cp4) CalculateQuinticControlPoints( + StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, StylusPoint p4, StylusPoint p5) + { + // 计算控制点距离(基于相邻点距离) + double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y)); + double dist2 = Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y)); + double dist3 = Math.Sqrt((p4.X - p3.X) * (p4.X - p3.X) + (p4.Y - p3.Y) * (p4.Y - p3.Y)); + double dist4 = Math.Sqrt((p5.X - p4.X) * (p5.X - p4.X) + (p5.Y - p4.Y) * (p5.Y - p4.Y)); + + // 使用更小的控制点距离,产生超平滑的曲线 + double controlDist1 = dist1 * 0.15; + double controlDist2 = dist2 * 0.15; + double controlDist3 = dist3 * 0.15; + double controlDist4 = dist4 * 0.15; + + // 计算控制点方向 - 使用更平滑的方向计算 + double dir1X = p2.X - p0.X; + double dir1Y = p2.Y - p0.Y; + double dir2X = p3.X - p1.X; + double dir2Y = p3.Y - p1.Y; + double dir3X = p4.X - p2.X; + double dir3Y = p4.Y - p2.Y; + double dir4X = p5.X - p3.X; + double dir4Y = p5.Y - p3.Y; + + // 归一化方向 + NormalizeVector(ref dir1X, ref dir1Y); + NormalizeVector(ref dir2X, ref dir2Y); + NormalizeVector(ref dir3X, ref dir3Y); + NormalizeVector(ref dir4X, ref dir4Y); + + // 计算控制点 + var cp1 = new Point(p1.X + dir1X * controlDist1, p1.Y + dir1Y * controlDist1); + var cp2 = new Point(p2.X + dir2X * controlDist2, p2.Y + dir2Y * controlDist2); + var cp3 = new Point(p3.X - dir3X * controlDist3, p3.Y - dir3Y * controlDist3); + var cp4 = new Point(p4.X - dir4X * controlDist4, p4.Y - dir4Y * controlDist4); + + return (cp1, cp2, cp3, cp4); + } + + /// + /// 归一化向量 + /// + private void NormalizeVector(ref double x, ref double y) + { + double length = Math.Sqrt(x * x + y * y); + if (length > 0) + { + x /= length; + y /= length; + } + } + + /// + /// 5次贝塞尔曲线点计算 + /// + private StylusPoint CalculateQuinticBezierPoint(StylusPoint p0, (Point cp1, Point cp2, Point cp3, Point cp4) controlPoints, StylusPoint p5, double t) + { + double oneMinusT = 1 - t; + double oneMinusT2 = oneMinusT * oneMinusT; + double oneMinusT3 = oneMinusT2 * oneMinusT; + double oneMinusT4 = oneMinusT3 * oneMinusT; + double oneMinusT5 = oneMinusT4 * oneMinusT; + + double t2 = t * t; + double t3 = t2 * t; + double t4 = t3 * t; + double t5 = t4 * t; + + // 5次贝塞尔曲线公式 + double x = oneMinusT5 * p0.X + + 5 * oneMinusT4 * t * controlPoints.cp1.X + + 10 * oneMinusT3 * t2 * controlPoints.cp2.X + + 10 * oneMinusT2 * t3 * controlPoints.cp3.X + + 5 * oneMinusT * t4 * controlPoints.cp4.X + + t5 * p5.X; + + double y = oneMinusT5 * p0.Y + + 5 * oneMinusT4 * t * controlPoints.cp1.Y + + 10 * oneMinusT3 * t2 * controlPoints.cp2.Y + + 10 * oneMinusT2 * t3 * controlPoints.cp3.Y + + 5 * oneMinusT * t4 * controlPoints.cp4.Y + + t5 * p5.Y; + + // 压力插值 - 使用线性插值 + float pressure = (float)((1 - t) * p0.PressureFactor + t * p5.PressureFactor); + + return new StylusPoint(x, y, Math.Max(pressure, 0.1f)); + } + + /// + /// 简化的控制点计算 + /// + private (Point cp1, Point cp2) CalculateSimpleControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3) + { + // 计算控制点距离(基于线段长度) + 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 * 0.2; // 进一步减少控制点影响 + double controlDist2 = dist2 * 0.2; + + // 计算控制点方向 - 使用更平滑的方向计算 + double dir1X = p2.X - p0.X; // 使用更远的点计算方向 + double dir1Y = p2.Y - p0.Y; + double dir2X = p3.X - p1.X; + double dir2Y = p3.Y - p1.Y; + + // 归一化方向 + double len1 = Math.Sqrt(dir1X * dir1X + dir1Y * dir1Y); + double len2 = Math.Sqrt(dir2X * dir2X + dir2Y * dir2Y); + + if (len1 > 0) + { + dir1X /= len1; + dir1Y /= len1; + } + + if (len2 > 0) + { + dir2X /= len2; + dir2Y /= len2; + } + + // 计算控制点 + var cp1 = new Point( + p1.X + dir1X * controlDist1, + p1.Y + dir1Y * controlDist1 + ); + + var cp2 = new Point( + p2.X - dir2X * controlDist2, + p2.Y - dir2Y * controlDist2 + ); + + return (cp1, cp2); + } + + /// + /// 宽松的去重算法 + /// + private StylusPoint[] RemoveDuplicatePointsLoose(StylusPoint[] points) + { + if (points.Length < 2) return points; + + var result = new List(); + result.Add(points[0]); + + double minDistance = 0.1; // 非常小的距离阈值,几乎不去重 + + for (int i = 1; i < points.Length; i++) + { + var lastPoint = result[result.Count - 1]; + var currentPoint = points[i]; + + // 计算距离 + double distance = Math.Sqrt( + (currentPoint.X - lastPoint.X) * (currentPoint.X - lastPoint.X) + + (currentPoint.Y - lastPoint.Y) * (currentPoint.Y - lastPoint.Y)); + + // 如果距离足够大,添加这个点 + if (distance >= minDistance) + { + result.Add(currentPoint); + } + } + + System.Diagnostics.Debug.WriteLine($"RemoveDuplicatePointsLoose: 输入点数={points.Length}, 输出点数={result.Count}"); + return result.ToArray(); + } + + /// + /// 计算贝塞尔曲线上的点 + /// + private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t) + { + double x = Math.Pow(1 - t, 3) * p0.X + + 3 * Math.Pow(1 - t, 2) * t * cp1.X + + 3 * (1 - t) * Math.Pow(t, 2) * cp2.X + + Math.Pow(t, 3) * p3.X; + + double y = Math.Pow(1 - t, 3) * p0.Y + + 3 * Math.Pow(1 - t, 2) * t * cp1.Y + + 3 * (1 - t) * Math.Pow(t, 2) * cp2.Y + + Math.Pow(t, 3) * p3.Y; + + // 压力插值 + float pressure = (float)(Math.Pow(1 - t, 3) * p0.PressureFactor + + 3 * Math.Pow(1 - t, 2) * t * p0.PressureFactor + + 3 * (1 - t) * Math.Pow(t, 2) * p3.PressureFactor + + Math.Pow(t, 3) * p3.PressureFactor); + + return new StylusPoint(x, y, Math.Max(pressure, 0.1f)); } /// @@ -251,7 +466,7 @@ namespace Ink_Canvas.Helpers var result = new List(); result.Add(points[0]); - double minDistance = ResampleInterval * 0.5; // 最小距离阈值 + double minDistance = 0.3; // 进一步减少最小距离阈值,保留更多平滑点 for (int i = 1; i < points.Length; i++) { @@ -270,6 +485,7 @@ namespace Ink_Canvas.Helpers } } + System.Diagnostics.Debug.WriteLine($"RemoveDuplicatePoints: 输入点数={points.Length}, 输出点数={result.Count}"); return result.ToArray(); } @@ -516,23 +732,31 @@ namespace Ink_Canvas.Helpers /// public class AdvancedBezierSmoothing { - public double SmoothingStrength { get; set; } = 0.3; - public double ResampleInterval { get; set; } = 3.0; - public int InterpolationSteps { get; set; } = 8; + public double SmoothingStrength { get; set; } = 0.6; + public double ResampleInterval { get; set; } = 2.0; + public int InterpolationSteps { get; set; } = 12; public Stroke SmoothStroke(Stroke stroke) { if (stroke == null || stroke.StylusPoints.Count < 3) + { + System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 笔画点数不足,跳过平滑 (点数: {stroke?.StylusPoints.Count ?? 0})"); return stroke; + } - var originalPoints = stroke.StylusPoints.ToList(); + System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 开始平滑处理,原始点数: {stroke.StylusPoints.Count}"); - // 简化处理:只进行轻度平滑 - var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑 + var originalPoints = stroke.StylusPoints.ToArray(); + + // 使用真正的贝塞尔曲线平滑 + var smoothedPoints = ApplyCubicBezierSmoothing(originalPoints); + + System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 平滑完成,平滑后点数: {smoothedPoints.Length}"); // 检查点数是否合理 - if (smoothedPoints.Count > originalPoints.Count * 1.5) + if (smoothedPoints.Length > originalPoints.Length * 10.0) { + System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 点数增加过多,返回原始笔画 (原始:{originalPoints.Length}, 平滑后:{smoothedPoints.Length})"); return stroke; // 如果点数增加太多,返回原始笔画 } @@ -540,9 +764,140 @@ namespace Ink_Canvas.Helpers { DrawingAttributes = stroke.DrawingAttributes.Clone() }; + + System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 创建平滑笔画成功"); return smoothedStroke; } + /// + /// 三次贝塞尔曲线平滑 + /// + private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points) + { + if (points.Length < 4) return points; + + var result = new List(); + result.Add(points[0]); + + // 使用更保守的窗口大小和插值 + int windowSize = Math.Min(4, points.Length); + int stepSize = Math.Max(1, points.Length / 10); // 根据点数动态调整步长 + + for (int i = 0; i <= points.Length - windowSize; i += stepSize) + { + if (i + windowSize - 1 >= points.Length) break; + + var p0 = points[i]; + var p1 = points[Math.Min(i + 1, points.Length - 1)]; + var p2 = points[Math.Min(i + 2, points.Length - 1)]; + var p3 = points[Math.Min(i + windowSize - 1, points.Length - 1)]; + + // 计算控制点 + var controlPoints = CalculateControlPoints(p0, p1, p2, p3); + + // 只生成2-3个插值点 + int steps = 2; + + // 生成贝塞尔曲线点 + for (int j = 1; j <= steps; j++) + { + double t = (double)j / steps; + var bezierPoint = CalculateBezierPoint(p0, controlPoints.cp1, controlPoints.cp2, p3, t); + result.Add(bezierPoint); + } + } + + result.Add(points[points.Length - 1]); + + // 去重处理 + return RemoveDuplicatePoints(result.ToArray()); + } + + /// + /// 去除重复点 + /// + private StylusPoint[] RemoveDuplicatePoints(StylusPoint[] points) + { + if (points.Length <= 1) return points; + + var result = new List { points[0] }; + double minDistance = 1.0; // 最小距离阈值 + + for (int i = 1; i < points.Length; i++) + { + var lastPoint = result[result.Count - 1]; + var currentPoint = points[i]; + + double distance = Math.Sqrt(Math.Pow(currentPoint.X - lastPoint.X, 2) + + Math.Pow(currentPoint.Y - lastPoint.Y, 2)); + + if (distance > minDistance) + { + result.Add(currentPoint); + } + } + + return result.ToArray(); + } + + /// + /// 计算控制点 + /// + private (Point cp1, Point cp2) CalculateControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3) + { + // 计算切线方向 + var tangent1 = new Vector(p1.X - p0.X, p1.Y - p0.Y); + var tangent2 = new Vector(p3.X - p2.X, p3.Y - p2.Y); + + // 归一化切线 + if (tangent1.Length > 0) tangent1.Normalize(); + if (tangent2.Length > 0) tangent2.Normalize(); + + // 计算控制点距离 + 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 * SmoothingStrength; + double controlDist2 = dist2 * SmoothingStrength; + + // 计算控制点 + var cp1 = new Point( + p1.X + tangent1.X * controlDist1, + p1.Y + tangent1.Y * controlDist1 + ); + + var cp2 = new Point( + p2.X - tangent2.X * controlDist2, + p2.Y - tangent2.Y * controlDist2 + ); + + return (cp1, cp2); + } + + /// + /// 计算贝塞尔曲线上的点 + /// + private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t) + { + double x = Math.Pow(1 - t, 3) * p0.X + + 3 * Math.Pow(1 - t, 2) * t * cp1.X + + 3 * (1 - t) * Math.Pow(t, 2) * cp2.X + + Math.Pow(t, 3) * p3.X; + + double y = Math.Pow(1 - t, 3) * p0.Y + + 3 * Math.Pow(1 - t, 2) * t * cp1.Y + + 3 * (1 - t) * Math.Pow(t, 2) * cp2.Y + + Math.Pow(t, 3) * p3.Y; + + // 压力插值 + float pressure = (float)(Math.Pow(1 - t, 3) * p0.PressureFactor + + 3 * Math.Pow(1 - t, 2) * t * p0.PressureFactor + + 3 * (1 - t) * Math.Pow(t, 2) * p3.PressureFactor + + Math.Pow(t, 3) * p3.PressureFactor); + + return new StylusPoint(x, y, Math.Max(pressure, 0.1f)); + } + /// /// 轻度指数平滑 /// diff --git a/Ink Canvas/Helpers/AutoBackupManager.cs b/Ink Canvas/Helpers/AutoBackupManager.cs new file mode 100644 index 00000000..badd4ecf --- /dev/null +++ b/Ink Canvas/Helpers/AutoBackupManager.cs @@ -0,0 +1,216 @@ +using Ink_Canvas.Helpers; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; + +namespace Ink_Canvas.Helpers +{ + /// + /// 自动备份管理器 + /// 负责管理配置文件的自动备份功能 + /// + public static class AutoBackupManager + { + private static readonly string BackupDir = Path.Combine(App.RootPath, "Backups"); + private static readonly string SettingsFile = Path.Combine(App.RootPath, "Configs", "Settings.json"); + private static readonly string BackupPrefix = "Settings_AutoBackup_"; + + /// + /// 检查是否需要执行自动备份 + /// + /// 设置对象 + /// 如果需要备份返回true,否则返回false + public static bool ShouldPerformAutoBackup(Settings settings) + { + try + { + // 如果自动备份功能未启用,不执行备份 + if (!settings.Advanced.IsAutoBackupEnabled) + { + return false; + } + + // 如果从未备份过,需要创建首次备份 + if (settings.Advanced.LastAutoBackupTime == DateTime.MinValue) + { + return true; + } + + // 检查是否已超过备份间隔 + var daysSinceLastBackup = (DateTime.Now - settings.Advanced.LastAutoBackupTime).TotalDays; + return daysSinceLastBackup >= settings.Advanced.AutoBackupIntervalDays; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查自动备份条件时出错: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 执行自动备份 + /// + /// 设置对象 + /// 备份是否成功 + public static bool PerformAutoBackup(Settings settings) + { + try + { + // 确保备份目录存在 + if (!Directory.Exists(BackupDir)) + { + Directory.CreateDirectory(BackupDir); + } + + // 检查主配置文件是否存在 + if (!File.Exists(SettingsFile)) + { + LogHelper.WriteLogToFile("主配置文件不存在,跳过自动备份", LogHelper.LogType.Warning); + return false; + } + + // 创建备份文件名(使用当前日期时间) + string backupFileName = $"{BackupPrefix}{DateTime.Now:yyyyMMdd_HHmmss}.json"; + string backupPath = Path.Combine(BackupDir, backupFileName); + + // 复制主配置文件到备份位置 + File.Copy(SettingsFile, backupPath, true); + + // 更新最后备份时间 + settings.Advanced.LastAutoBackupTime = DateTime.Now; + MainWindow.SaveSettingsToFile(); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"执行自动备份时出错: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 尝试从备份恢复配置文件 + /// + /// 恢复是否成功 + public static bool TryRestoreFromBackup() + { + try + { + // 确保备份目录存在 + if (!Directory.Exists(BackupDir)) + { + LogHelper.WriteLogToFile("备份目录不存在,无法从备份恢复", LogHelper.LogType.Warning); + return false; + } + + // 查找最新的备份文件 + var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json") + .OrderByDescending(f => File.GetCreationTime(f)) + .ToArray(); + + if (backupFiles.Length == 0) + { + LogHelper.WriteLogToFile("没有找到可用的备份文件", LogHelper.LogType.Warning); + return false; + } + + // 尝试使用最新的备份文件 + string latestBackup = backupFiles[0]; + + // 验证备份文件是否有效 + try + { + string backupJson = File.ReadAllText(latestBackup); + var testSettings = JsonConvert.DeserializeObject(backupJson); + if (testSettings == null) + { + LogHelper.WriteLogToFile("备份文件内容无效,无法恢复", LogHelper.LogType.Error); + return false; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"备份文件验证失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + + // 备份当前损坏的配置文件(如果存在) + if (File.Exists(SettingsFile)) + { + string corruptedBackup = Path.Combine(BackupDir, $"Settings_Corrupted_{DateTime.Now:yyyyMMdd_HHmmss}.json"); + File.Copy(SettingsFile, corruptedBackup, true); + } + + // 从备份恢复配置文件 + File.Copy(latestBackup, SettingsFile, true); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从备份恢复配置文件时出错: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 清理过期的备份文件 + /// 保留最近30天的备份文件 + /// + public static void CleanupOldBackups() + { + try + { + if (!Directory.Exists(BackupDir)) + { + return; + } + + var cutoffDate = DateTime.Now.AddDays(-30); + var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json"); + + int deletedCount = 0; + foreach (var file in backupFiles) + { + if (File.GetCreationTime(file) < cutoffDate) + { + File.Delete(file); + deletedCount++; + } + } + + if (deletedCount > 0) + { + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理过期备份文件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 初始化自动备份功能 + /// 在应用程序启动时调用 + /// + /// 设置对象 + public static void Initialize(Settings settings) + { + try + { + // 检查是否需要执行自动备份 + if (ShouldPerformAutoBackup(settings)) + { + PerformAutoBackup(settings); + } + + // 清理过期备份 + CleanupOldBackups(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化自动备份功能时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/CameraService.cs b/Ink Canvas/Helpers/CameraService.cs index da9430dc..449e31fe 100644 --- a/Ink Canvas/Helpers/CameraService.cs +++ b/Ink Canvas/Helpers/CameraService.cs @@ -47,7 +47,6 @@ namespace Ink_Canvas.Helpers AvailableCameras.Add(device); } - LogHelper.WriteLogToFile($"发现 {AvailableCameras.Count} 个摄像头设备"); } catch (Exception ex) { @@ -120,7 +119,6 @@ namespace Ink_Canvas.Helpers } _isCapturing = false; - LogHelper.WriteLogToFile("摄像头预览已停止"); } catch (Exception ex) { diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs index 0fc47e56..4929c50c 100644 --- a/Ink Canvas/Helpers/DeviceIdentifier.cs +++ b/Ink Canvas/Helpers/DeviceIdentifier.cs @@ -1065,7 +1065,6 @@ namespace Ink_Canvas.Helpers // 如果不是自动更新(即版本修复),则应用不同的策略 if (!isAutoUpdate) { - // 版本修复:立即允许,不受分级策略影响 LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复 - 版本: {updateVersion}, 类型: {updateType}, 结果: 允许"); return true; } diff --git a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs index 33ffafac..665099e2 100644 --- a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs +++ b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Windows.Threading; namespace Ink_Canvas.Helpers @@ -19,6 +19,9 @@ namespace Ink_Canvas.Helpers [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); + [DllImport("user32.dll")] + private static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc enumProc, IntPtr lParam); + [DllImport("user32.dll")] private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint processId); @@ -110,6 +113,10 @@ namespace Ink_Canvas.Helpers /// SeewoPincoDrawingFloating, /// + /// 希沃品课教师端 桌面画板 + /// + SeewoPincoBoardService, + /// /// 希沃PPT小工具 /// SeewoPPTFloating, @@ -122,18 +129,66 @@ namespace Ink_Canvas.Helpers /// HiteAnnotationFloating, /// - /// 畅言智慧课堂 桌面悬浮窗 + /// 畅言智慧课堂 主栏悬浮窗 /// ChangYanFloating, /// + /// 畅言智慧课堂 画笔设置 + /// + ChangYanBrushSettings, + /// + /// 畅言智慧课堂 滑动清除 + /// + ChangYanSwipeClear, + /// + /// 畅言智慧课堂 互动 + /// + ChangYanInteraction, + /// + /// 畅言智慧课堂 学科应用 + /// + ChangYanSubjectApp, + /// + /// 畅言智慧课堂 管控 + /// + ChangYanControl, + /// + /// 畅言智慧课堂 通用工具 + /// + ChangYanCommonTools, + /// + /// 畅言智慧课堂 场景工具栏 + /// + ChangYanSceneToolbar, + /// + /// 畅言智慧课堂 绘制窗口 + /// + ChangYanDrawWindow, + /// /// 畅言智慧课堂 PPT悬浮窗 /// ChangYanPptFloating, /// + /// 畅言智慧课堂 PPT页面控制 + /// + ChangYanPptPageControl, + /// + /// 畅言智慧课堂 PPT返回 + /// + ChangYanPptGoBack, + /// + /// 畅言智慧课堂 PPT预览 + /// + ChangYanPptPreview, + /// /// 天喻教育云互动课堂 桌面悬浮窗(包括PPT控件) /// IntelligentClassFloating, /// + /// 天喻教育云互动课堂 PPT悬浮窗 + /// + IntelligentClassPptFloating, + /// /// 希沃桌面 画笔悬浮窗 /// SeewoDesktopAnnotationFloating, @@ -155,6 +210,8 @@ namespace Ink_Canvas.Helpers public bool IsEnabled { get; set; } public bool RequiresAdmin { get; set; } public string Description { get; set; } + public InterceptType? ParentType { get; set; } + public List ChildTypes { get; set; } = new List(); } #endregion @@ -167,6 +224,14 @@ namespace Ink_Canvas.Helpers private readonly Dispatcher _dispatcher; private bool _isRunning; private bool _disposed; + + // 性能优化字段 + private readonly Dictionary _lastScanTime = new Dictionary(); + private readonly HashSet _knownWindows = new HashSet(); + private readonly Dictionary _processLastScanTime = new Dictionary(); + private int _consecutiveEmptyScans = 0; + private DateTime _lastSuccessfulScan = DateTime.Now; + private readonly object _scanLock = new object(); #endregion @@ -206,8 +271,8 @@ namespace Ink_Canvas.Helpers { Type = InterceptType.SeewoWhiteboard3Floating, ProcessName = "EasiNote", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "Note", + ClassNamePattern = "HwndWrapper[EasiNote.exe;;", IsEnabled = true, RequiresAdmin = false, Description = "希沃白板3 桌面悬浮窗" @@ -219,7 +284,7 @@ namespace Ink_Canvas.Helpers Type = InterceptType.SeewoWhiteboard5Floating, ProcessName = "EasiNote", WindowTitlePattern = "", - ClassNamePattern = "", + ClassNamePattern = "HwndWrapper[EasiNote;;", IsEnabled = true, RequiresAdmin = false, Description = "希沃白板5 桌面悬浮窗" @@ -231,43 +296,61 @@ namespace Ink_Canvas.Helpers Type = InterceptType.SeewoWhiteboard5CFloating, ProcessName = "EasiNote5C", WindowTitlePattern = "", - ClassNamePattern = "", + ClassNamePattern = "HwndWrapper[EasiNote5C;;", IsEnabled = true, RequiresAdmin = false, Description = "希沃白板5C 桌面悬浮窗" }; - // 希沃品课教师端 桌面悬浮窗 + // 希沃品课教师端 桌面悬浮窗(父规则) _interceptRules[InterceptType.SeewoPincoSideBarFloating] = new InterceptRule { Type = InterceptType.SeewoPincoSideBarFloating, ProcessName = "ClassIn", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "希沃品课——appBar", + ClassNamePattern = "Chrome_WidgetWin_1", IsEnabled = true, RequiresAdmin = false, - Description = "希沃品课教师端 桌面悬浮窗" + Description = "希沃品课教师端 桌面悬浮窗", + ParentType = null, + ChildTypes = new List { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService } }; - // 希沃品课教师端 画笔悬浮窗 + // 希沃品课教师端 画笔悬浮窗(子规则) _interceptRules[InterceptType.SeewoPincoDrawingFloating] = new InterceptRule { Type = InterceptType.SeewoPincoDrawingFloating, ProcessName = "ClassIn", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "希沃品课——integration", + ClassNamePattern = "Chrome_WidgetWin_1", IsEnabled = true, RequiresAdmin = false, - Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)" + Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)", + ParentType = InterceptType.SeewoPincoSideBarFloating, + ChildTypes = new List() + }; + + // 希沃品课教师端 桌面画板(子规则) + _interceptRules[InterceptType.SeewoPincoBoardService] = new InterceptRule + { + Type = InterceptType.SeewoPincoBoardService, + ProcessName = "BoardService", + WindowTitlePattern = "", + ClassNamePattern = "HwndWrapper[BoardService;;", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃品课教师端 桌面画板", + ParentType = InterceptType.SeewoPincoSideBarFloating, + ChildTypes = new List() }; // 希沃PPT小工具 _interceptRules[InterceptType.SeewoPPTFloating] = new InterceptRule { Type = InterceptType.SeewoPPTFloating, - ProcessName = "SeewoPPT", + ProcessName = "PPTService", WindowTitlePattern = "", - ClassNamePattern = "", + ClassNamePattern = "HwndWrapper[PPTService.exe;;", IsEnabled = true, RequiresAdmin = false, Description = "希沃PPT小工具" @@ -278,8 +361,8 @@ namespace Ink_Canvas.Helpers { Type = InterceptType.AiClassFloating, ProcessName = "ClassIn", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "TransparentWindow", + ClassNamePattern = "UIWndTransparent", IsEnabled = true, RequiresAdmin = false, Description = "AiClass 桌面悬浮窗" @@ -290,23 +373,147 @@ namespace Ink_Canvas.Helpers { Type = InterceptType.HiteAnnotationFloating, ProcessName = "HiteVision", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "HiteAnnotation", + ClassNamePattern = "Qt5QWindowToolSaveBits", IsEnabled = true, RequiresAdmin = false, Description = "鸿合屏幕书写" }; - // 畅言智慧课堂 桌面悬浮窗 + // 畅言智慧课堂 主栏悬浮窗(父规则) _interceptRules[InterceptType.ChangYanFloating] = new InterceptRule { Type = InterceptType.ChangYanFloating, ProcessName = "ClassIn", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "ifly", + ClassNamePattern = "Qt5QWindowOwnDCIcon", IsEnabled = true, RequiresAdmin = true, - Description = "畅言智慧课堂 桌面悬浮窗" + Description = "畅言智慧课堂 主栏悬浮窗", + ParentType = null, + ChildTypes = new List + { + InterceptType.ChangYanBrushSettings, + InterceptType.ChangYanSwipeClear, + InterceptType.ChangYanInteraction, + InterceptType.ChangYanSubjectApp, + InterceptType.ChangYanControl, + InterceptType.ChangYanCommonTools, + InterceptType.ChangYanSceneToolbar, + InterceptType.ChangYanDrawWindow + } + }; + + // 畅言智慧课堂 画笔设置(子规则) + _interceptRules[InterceptType.ChangYanBrushSettings] = new InterceptRule + { + Type = InterceptType.ChangYanBrushSettings, + ProcessName = "ClassIn", + WindowTitlePattern = "画笔设置", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 画笔设置", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 滑动清除(子规则) + _interceptRules[InterceptType.ChangYanSwipeClear] = new InterceptRule + { + Type = InterceptType.ChangYanSwipeClear, + ProcessName = "ClassIn", + WindowTitlePattern = "滑动清除", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 滑动清除", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 互动(子规则) + _interceptRules[InterceptType.ChangYanInteraction] = new InterceptRule + { + Type = InterceptType.ChangYanInteraction, + ProcessName = "ClassIn", + WindowTitlePattern = "互动", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 互动", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 学科应用(子规则) + _interceptRules[InterceptType.ChangYanSubjectApp] = new InterceptRule + { + Type = InterceptType.ChangYanSubjectApp, + ProcessName = "ClassIn", + WindowTitlePattern = "学科应用", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 学科应用", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 管控(子规则) + _interceptRules[InterceptType.ChangYanControl] = new InterceptRule + { + Type = InterceptType.ChangYanControl, + ProcessName = "ClassIn", + WindowTitlePattern = "管控", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 管控", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 通用工具(子规则) + _interceptRules[InterceptType.ChangYanCommonTools] = new InterceptRule + { + Type = InterceptType.ChangYanCommonTools, + ProcessName = "ClassIn", + WindowTitlePattern = "通用工具", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 通用工具", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 场景工具栏(子规则) + _interceptRules[InterceptType.ChangYanSceneToolbar] = new InterceptRule + { + Type = InterceptType.ChangYanSceneToolbar, + ProcessName = "ClassIn", + WindowTitlePattern = "SceneToolbar", + ClassNamePattern = "Qt5QWindowOwnDCIcon", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 场景工具栏", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 绘制窗口(子规则) + _interceptRules[InterceptType.ChangYanDrawWindow] = new InterceptRule + { + Type = InterceptType.ChangYanDrawWindow, + ProcessName = "ClassIn", + WindowTitlePattern = "DrawWindow", + ClassNamePattern = "Qt5QWindowToolSaveBits", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 绘制窗口", + ParentType = InterceptType.ChangYanFloating, + ChildTypes = new List() }; // 畅言智慧课堂 PPT悬浮窗 @@ -314,32 +521,92 @@ namespace Ink_Canvas.Helpers { Type = InterceptType.ChangYanPptFloating, ProcessName = "ClassIn", - WindowTitlePattern = "", - ClassNamePattern = "", + WindowTitlePattern = "Exch", + ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC", IsEnabled = true, RequiresAdmin = true, - Description = "畅言智慧课堂 PPT悬浮窗" + Description = "畅言智慧课堂 PPT悬浮窗", + ParentType = null, + ChildTypes = new List { InterceptType.ChangYanPptPageControl, InterceptType.ChangYanPptGoBack, InterceptType.ChangYanPptPreview } }; - // 天喻教育云互动课堂 桌面悬浮窗 + // 畅言智慧课堂 PPT页面控制(子规则) + _interceptRules[InterceptType.ChangYanPptPageControl] = new InterceptRule + { + Type = InterceptType.ChangYanPptPageControl, + ProcessName = "ClassIn", + WindowTitlePattern = "PageCtl", + ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 PPT页面控制", + ParentType = InterceptType.ChangYanPptFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 PPT返回(子规则) + _interceptRules[InterceptType.ChangYanPptGoBack] = new InterceptRule + { + Type = InterceptType.ChangYanPptGoBack, + ProcessName = "ClassIn", + WindowTitlePattern = "Goback", + ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 PPT返回", + ParentType = InterceptType.ChangYanPptFloating, + ChildTypes = new List() + }; + + // 畅言智慧课堂 PPT预览(子规则) + _interceptRules[InterceptType.ChangYanPptPreview] = new InterceptRule + { + Type = InterceptType.ChangYanPptPreview, + ProcessName = "ClassIn", + WindowTitlePattern = "Preview", + ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 PPT预览", + ParentType = InterceptType.ChangYanPptFloating, + ChildTypes = new List() + }; + + // 天喻教育云互动课堂 桌面悬浮窗(父规则) _interceptRules[InterceptType.IntelligentClassFloating] = new InterceptRule { Type = InterceptType.IntelligentClassFloating, - ProcessName = "ClassIn", - WindowTitlePattern = "", - ClassNamePattern = "", + ProcessName = "IntelligentClassApp", + WindowTitlePattern = "桌面小工具 - 互动课堂", + ClassNamePattern = "HwndWrapper[IntelligentClassApp.exe;;", IsEnabled = true, RequiresAdmin = false, - Description = "天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)" + Description = "天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)", + ParentType = null, + ChildTypes = new List { InterceptType.IntelligentClassPptFloating } + }; + + // 天喻教育云互动课堂 PPT悬浮窗(子规则) + _interceptRules[InterceptType.IntelligentClassPptFloating] = new InterceptRule + { + Type = InterceptType.IntelligentClassPptFloating, + ProcessName = "IntelligentClass", + WindowTitlePattern = "", + ClassNamePattern = "HwndWrapper[IntelligentClass.Office.PowerPoint.vsto|vstolocal;VSTA_Main;", + IsEnabled = true, + RequiresAdmin = false, + Description = "天喻教育云互动课堂 PPT悬浮窗", + ParentType = InterceptType.IntelligentClassFloating, + ChildTypes = new List() }; // 希沃桌面 画笔悬浮窗 _interceptRules[InterceptType.SeewoDesktopAnnotationFloating] = new InterceptRule { Type = InterceptType.SeewoDesktopAnnotationFloating, - ProcessName = "Seewo", + ProcessName = "DesktopAnnotation", WindowTitlePattern = "", - ClassNamePattern = "", + ClassNamePattern = "HwndWrapper[DesktopAnnotation.exe;;", IsEnabled = true, RequiresAdmin = false, Description = "希沃桌面 画笔悬浮窗" @@ -349,9 +616,9 @@ namespace Ink_Canvas.Helpers _interceptRules[InterceptType.SeewoDesktopSideBarFloating] = new InterceptRule { Type = InterceptType.SeewoDesktopSideBarFloating, - ProcessName = "Seewo", - WindowTitlePattern = "", - ClassNamePattern = "", + ProcessName = "ResidentSideBar", + WindowTitlePattern = "ResidentSideBar", + ClassNamePattern = "HwndWrapper[ResidentSideBar.exe;;", IsEnabled = true, RequiresAdmin = true, Description = "希沃桌面 侧栏悬浮窗" @@ -371,7 +638,7 @@ namespace Ink_Canvas.Helpers if (_isRunning) return; _isRunning = true; - _scanTimer.Change(0, scanIntervalMs); + _scanTimer.Change(0, Math.Max(scanIntervalMs, 2000)); } /// @@ -395,7 +662,51 @@ namespace Ink_Canvas.Helpers { if (_interceptRules.ContainsKey(type)) { - _interceptRules[type].IsEnabled = enabled; + var rule = _interceptRules[type]; + rule.IsEnabled = enabled; + + // 如果是父规则被禁用,则禁用所有子规则 + if (!enabled && rule.ChildTypes.Count > 0) + { + foreach (var childType in rule.ChildTypes) + { + if (_interceptRules.ContainsKey(childType)) + { + _interceptRules[childType].IsEnabled = false; + } + } + } + // 如果是父规则被启用,则启用所有子规则 + else if (enabled && rule.ChildTypes.Count > 0) + { + foreach (var childType in rule.ChildTypes) + { + if (_interceptRules.ContainsKey(childType)) + { + _interceptRules[childType].IsEnabled = true; + } + } + } + // 如果是子规则被禁用,检查是否需要禁用父规则 + else if (!enabled && rule.ParentType.HasValue) + { + var parentRule = _interceptRules[rule.ParentType.Value]; + // 检查是否还有其他启用的子规则 + bool hasEnabledChildren = parentRule.ChildTypes.Any(childType => + _interceptRules.ContainsKey(childType) && _interceptRules[childType].IsEnabled); + + // 如果没有启用的子规则,则禁用父规则 + if (!hasEnabledChildren) + { + parentRule.IsEnabled = false; + } + } + // 如果是子规则被启用,则启用父规则 + else if (enabled && rule.ParentType.HasValue) + { + var parentRule = _interceptRules[rule.ParentType.Value]; + parentRule.IsEnabled = true; + } } } @@ -468,18 +779,106 @@ namespace Ink_Canvas.Helpers { if (!_isRunning) return; - try + lock (_scanLock) { - EnumWindows(EnumWindowsCallback, IntPtr.Zero); - } - catch (Exception ex) - { - // 记录错误但不中断扫描 - LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + try + { + var scanStartTime = DateTime.Now; + var windowsFound = 0; + var windowsIntercepted = 0; + + // 清理过期的缓存 + CleanupExpiredCache(); + + // 使用优化的扫描策略 + if (_consecutiveEmptyScans > 3) + { + // 如果连续多次扫描没有发现新窗口,使用快速扫描模式 + PerformQuickScan(ref windowsFound, ref windowsIntercepted); + } + else + { + // 正常扫描模式 + PerformFullScan(ref windowsFound, ref windowsIntercepted); + } + + // 更新扫描统计 + UpdateScanStatistics(windowsFound, windowsIntercepted, scanStartTime); + + // 动态调整扫描间隔 + AdjustScanInterval(); + } + catch (Exception ex) + { + // 记录错误但不中断扫描 + LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + _consecutiveEmptyScans++; + } } } - private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam) + /// + /// 执行快速扫描 - 只检查已知进程 + /// + private void PerformQuickScan(ref int windowsFound, ref int windowsIntercepted) + { + var targetProcesses = new HashSet(); + var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 }; + + // 收集所有启用的规则对应的进程名 + foreach (var rule in _interceptRules.Values) + { + if (rule.IsEnabled && !string.IsNullOrEmpty(rule.ProcessName)) + { + targetProcesses.Add(rule.ProcessName.ToLower()); + } + } + + // 只扫描目标进程的窗口 + foreach (var processName in targetProcesses) + { + try + { + var processes = Process.GetProcessesByName(processName); + foreach (var process in processes) + { + if (process.MainWindowHandle != IntPtr.Zero) + { + ProcessWindow(process.MainWindowHandle, scanData); + } + } + } + catch + { + // 忽略进程访问错误 + } + } + + windowsFound = scanData.WindowsFound; + windowsIntercepted = scanData.WindowsIntercepted; + } + + /// + /// 执行完整扫描 + /// + private void PerformFullScan(ref int windowsFound, ref int windowsIntercepted) + { + var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 }; + + EnumWindows((hWnd, lParam) => + { + ProcessWindow(hWnd, scanData); + return true; + }, IntPtr.Zero); + + windowsFound = scanData.WindowsFound; + windowsIntercepted = scanData.WindowsIntercepted; + } + + /// + /// 处理单个窗口 + /// + private bool ProcessWindow(IntPtr hWnd, ScanData scanData) { try { @@ -489,10 +888,35 @@ namespace Ink_Canvas.Helpers // 检查是否已经被拦截 if (_interceptedWindows.ContainsKey(hWnd)) return true; + // 检查缓存,避免重复处理 + if (_knownWindows.Contains(hWnd)) + { + var lastScan = _lastScanTime.ContainsKey(hWnd) ? _lastScanTime[hWnd] : DateTime.MinValue; + if (DateTime.Now - lastScan < TimeSpan.FromSeconds(30)) // 30秒内不重复处理 + { + return true; + } + } + + scanData.WindowsFound++; + _knownWindows.Add(hWnd); + _lastScanTime[hWnd] = DateTime.Now; + // 获取窗口信息 var windowInfo = GetWindowInfo(hWnd); if (windowInfo == null) return true; + // 检查进程缓存 + if (_processLastScanTime.ContainsKey(windowInfo.ProcessName)) + { + var lastProcessScan = _processLastScanTime[windowInfo.ProcessName]; + if (DateTime.Now - lastProcessScan < TimeSpan.FromSeconds(10)) // 10秒内不重复扫描同一进程 + { + return true; + } + } + _processLastScanTime[windowInfo.ProcessName] = DateTime.Now; + // 检查窗口样式,过滤掉系统窗口和主窗口 var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE); var style = GetWindowLong(hWnd, -16); // GWL_STYLE @@ -530,6 +954,7 @@ namespace Ink_Canvas.Helpers if (MatchesRule(windowInfo, rule)) { InterceptWindow(hWnd, rule); + scanData.WindowsIntercepted++; break; } } @@ -543,6 +968,102 @@ namespace Ink_Canvas.Helpers } } + /// + /// 清理过期缓存 + /// + private void CleanupExpiredCache() + { + var now = DateTime.Now; + var expiredWindows = new List(); + var expiredProcesses = new List(); + + // 清理窗口缓存 + foreach (var kvp in _lastScanTime) + { + if (now - kvp.Value > TimeSpan.FromMinutes(5)) + { + expiredWindows.Add(kvp.Key); + } + } + + foreach (var hWnd in expiredWindows) + { + _lastScanTime.Remove(hWnd); + _knownWindows.Remove(hWnd); + } + + // 清理进程缓存 + foreach (var kvp in _processLastScanTime) + { + if (now - kvp.Value > TimeSpan.FromMinutes(2)) + { + expiredProcesses.Add(kvp.Key); + } + } + + foreach (var processName in expiredProcesses) + { + _processLastScanTime.Remove(processName); + } + } + + /// + /// 更新扫描统计 + /// + private void UpdateScanStatistics(int windowsFound, int windowsIntercepted, DateTime scanStartTime) + { + var scanDuration = DateTime.Now - scanStartTime; + + if (windowsFound == 0) + { + _consecutiveEmptyScans++; + } + else + { + _consecutiveEmptyScans = 0; + _lastSuccessfulScan = DateTime.Now; + } + } + + /// + /// 动态调整扫描间隔 + /// + private void AdjustScanInterval() + { + if (!_isRunning) return; + + int newInterval; + if (_consecutiveEmptyScans > 5) + { + // 连续多次空扫描,增加间隔到30秒 + newInterval = 30000; + } + else if (_consecutiveEmptyScans > 3) + { + // 连续多次空扫描,增加间隔到15秒 + newInterval = 15000; + } + else if (_consecutiveEmptyScans > 1) + { + // 连续空扫描,增加间隔到10秒 + newInterval = 10000; + } + else + { + // 正常扫描,使用5秒间隔 + newInterval = 5000; + } + + // 更新定时器间隔 + _scanTimer.Change(newInterval, newInterval); + } + + private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam) + { + // 这个方法现在由ProcessWindow替代,保留用于兼容性 + return true; + } + private WindowInfo GetWindowInfo(IntPtr hWnd) { try @@ -722,6 +1243,12 @@ namespace Ink_Canvas.Helpers public Process Process { get; set; } } + private class ScanData + { + public int WindowsFound { get; set; } + public int WindowsIntercepted { get; set; } + } + #endregion #region 事件参数类 diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs index 70d6ecb6..917ee3f8 100644 --- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs +++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; +using System.Windows; using System.Windows.Input; +using System.Windows.Forms; namespace Ink_Canvas.Helpers { @@ -19,6 +21,16 @@ namespace Ink_Canvas.Helpers private readonly MainWindow _mainWindow; private bool _isDisposed; private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键 + + // 多屏幕支持相关字段 + private Screen _currentScreen; + private bool _isMultiScreenMode = false; + private bool _enableScreenSpecificHotkeys = true; // 是否启用基于屏幕的热键注册 + + // 智能热键管理相关字段 + private bool _isWindowFocused = false; + private bool _isMouseOverWindow = false; + private System.Windows.Threading.DispatcherTimer _mousePositionTimer; // 配置文件路径 private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "Configs", "HotkeyConfig.json"); @@ -31,6 +43,9 @@ namespace Ink_Canvas.Helpers _registeredHotkeys = new Dictionary(); _hotkeysShouldBeRegistered = true; // 启动时注册热键 + // 初始化多屏幕支持 + InitializeMultiScreenSupport(); + // 启动时确保配置文件存在 EnsureConfigFileExists(); } @@ -52,6 +67,12 @@ namespace Ink_Canvas.Helpers if (_isDisposed) return false; + // 检查是否应该注册热键(基于屏幕和模式) + if (!ShouldRegisterHotkeys()) + { + return false; + } + // 如果快捷键已存在,先注销 if (_registeredHotkeys.ContainsKey(hotkeyName)) { @@ -85,7 +106,10 @@ namespace Ink_Canvas.Helpers }); _registeredHotkeys[hotkeyName] = hotkeyInfo; - // 成功注册全局快捷键 + + // 记录注册信息 + var screenInfo = _isMultiScreenMode ? $" (屏幕: {_currentScreen?.DeviceName})" : ""; + return true; } catch (Exception ex) @@ -179,7 +203,6 @@ namespace Ink_Canvas.Helpers { if (!File.Exists(HotkeyConfigFile)) { - LogHelper.WriteLogToFile("快捷键配置文件不存在"); return new List(); } @@ -212,7 +235,6 @@ namespace Ink_Canvas.Helpers }); } - LogHelper.WriteLogToFile($"从配置文件读取到 {hotkeyList.Count} 个快捷键信息"); return hotkeyList; } catch (Exception ex) @@ -287,7 +309,6 @@ namespace Ink_Canvas.Helpers if (!File.Exists(HotkeyConfigFile)) { LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning); - LogHelper.WriteLogToFile("配置文件不存在,创建默认配置文件并注册默认快捷键"); CreateDefaultConfigFile(); RegisterDefaultHotkeys(); _hotkeysShouldBeRegistered = true; @@ -299,7 +320,6 @@ namespace Ink_Canvas.Helpers { // 成功从配置文件加载快捷键设置 _hotkeysShouldBeRegistered = true; - LogHelper.WriteLogToFile("成功从配置文件加载快捷键设置"); } else { @@ -322,11 +342,9 @@ namespace Ink_Canvas.Helpers { try { - LogHelper.WriteLogToFile("开始保存快捷键配置到配置文件", LogHelper.LogType.Event); if (SaveHotkeysToConfigFile()) { - LogHelper.WriteLogToFile("快捷键配置已成功保存到配置文件", LogHelper.LogType.Event); } else { @@ -350,10 +368,18 @@ namespace Ink_Canvas.Helpers if (!_hotkeysShouldBeRegistered) { _hotkeysShouldBeRegistered = true; - LogHelper.WriteLogToFile("启用快捷键注册功能"); - // 立即加载快捷键设置 - LoadHotkeysFromSettings(); + // 启动鼠标位置监控定时器 + if (_isMultiScreenMode && _enableScreenSpecificHotkeys && _mousePositionTimer != null) + { + _mousePositionTimer.Start(); + } + + // 根据上下文决定是否立即加载快捷键 + if (ShouldEnableHotkeysBasedOnContext()) + { + LoadHotkeysFromSettings(); + } } else { @@ -377,12 +403,17 @@ namespace Ink_Canvas.Helpers { _hotkeysShouldBeRegistered = false; + // 停止鼠标位置监控定时器 + if (_mousePositionTimer != null && _mousePositionTimer.IsEnabled) + { + _mousePositionTimer.Stop(); + } + // 注销所有快捷键 UnregisterAllHotkeys(); } else { - LogHelper.WriteLogToFile("快捷键注册功能已经禁用"); } } catch (Exception ex) @@ -407,7 +438,6 @@ namespace Ink_Canvas.Helpers { // 如果设置允许,则在鼠标模式下也启用快捷键 EnableHotkeyRegistration(); - LogHelper.WriteLogToFile("切换到鼠标模式,但根据设置保持快捷键启用"); } else { @@ -455,7 +485,6 @@ namespace Ink_Canvas.Helpers if (success) { - LogHelper.WriteLogToFile($"成功更新快捷键 {hotkeyName}: {modifiers}+{key}", LogHelper.LogType.Event); // 自动保存配置 SaveHotkeysToSettings(); } @@ -468,9 +497,418 @@ namespace Ink_Canvas.Helpers return false; } } + + /// + /// 启用基于屏幕的热键注册 + /// + public void EnableScreenSpecificHotkeys() + { + try + { + _enableScreenSpecificHotkeys = true; + + // 如果当前在多屏幕环境下,刷新热键注册 + if (_isMultiScreenMode) + { + RefreshHotkeysForCurrentScreen(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启用基于屏幕的热键注册时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 禁用基于屏幕的热键注册 + /// + public void DisableScreenSpecificHotkeys() + { + try + { + _enableScreenSpecificHotkeys = false; + + // 重新注册热键(全局模式) + if (_hotkeysShouldBeRegistered) + { + RefreshHotkeysForCurrentScreen(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"禁用基于屏幕的热键注册时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 获取当前屏幕信息 + /// + /// 当前屏幕信息 + public string GetCurrentScreenInfo() + { + try + { + if (_isMultiScreenMode && _currentScreen != null) + { + return $"多屏幕环境 - 当前屏幕: {_currentScreen.DeviceName} ({_currentScreen.Bounds.Width}x{_currentScreen.Bounds.Height})"; + } + else + { + return "单屏幕环境"; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取当前屏幕信息时出错: {ex.Message}", LogHelper.LogType.Error); + return "无法获取屏幕信息"; + } + } + + /// + /// 检查是否启用了基于屏幕的热键注册 + /// + /// 是否启用 + public bool IsScreenSpecificHotkeysEnabled() + { + return _enableScreenSpecificHotkeys && _isMultiScreenMode; + } + + /// + /// 手动刷新当前屏幕的热键注册 + /// + public void RefreshCurrentScreenHotkeys() + { + try + { + RefreshHotkeysForCurrentScreen(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"刷新当前屏幕热键时出错: {ex.Message}", LogHelper.LogType.Error); + } + } #endregion #region Private Helper Methods + /// + /// 初始化多屏幕支持 + /// + private void InitializeMultiScreenSupport() + { + try + { + // 检测是否有多个屏幕 + _isMultiScreenMode = ScreenDetectionHelper.HasMultipleScreens(); + + if (_isMultiScreenMode) + { + // 获取当前窗口所在的屏幕 + _currentScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow); + + // 监听窗口位置变化事件 + _mainWindow.LocationChanged += OnWindowLocationChanged; + + // 初始化智能热键管理 + InitializeSmartHotkeyManagement(); + } + else + { + _currentScreen = ScreenDetectionHelper.GetPrimaryScreen(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化多屏幕支持时出错: {ex.Message}", LogHelper.LogType.Error); + _isMultiScreenMode = false; + _currentScreen = ScreenDetectionHelper.GetPrimaryScreen(); + } + } + + /// + /// 初始化智能热键管理 + /// + private void InitializeSmartHotkeyManagement() + { + try + { + // 监听窗口焦点事件 + _mainWindow.GotFocus += OnWindowGotFocus; + _mainWindow.LostFocus += OnWindowLostFocus; + + // 监听鼠标进入/离开事件 + _mainWindow.MouseEnter += OnMouseEnterWindow; + _mainWindow.MouseLeave += OnMouseLeaveWindow; + + // 初始化鼠标位置监控定时器 + _mousePositionTimer = new System.Windows.Threading.DispatcherTimer(); + _mousePositionTimer.Interval = TimeSpan.FromMilliseconds(500); // 每500ms检查一次 + _mousePositionTimer.Tick += OnMousePositionTimerTick; + + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化热键管理时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口位置变化事件处理 + /// + private void OnWindowLocationChanged(object sender, EventArgs e) + { + try + { + if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys) + return; + + var newScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow); + if (newScreen != null && newScreen != _currentScreen) + { + _currentScreen = newScreen; + + // 重新注册热键以适应新屏幕 + RefreshHotkeysForCurrentScreen(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口位置变化时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 为当前屏幕刷新热键注册 + /// + private void RefreshHotkeysForCurrentScreen() + { + try + { + if (!_hotkeysShouldBeRegistered) + return; + + // 注销所有现有热键 + UnregisterAllHotkeys(); + + // 重新注册热键 + LoadHotkeysFromSettings(); + + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"刷新当前屏幕热键时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口获得焦点事件处理 + /// + private void OnWindowGotFocus(object sender, RoutedEventArgs e) + { + try + { + _isWindowFocused = true; + UpdateHotkeyStateBasedOnContext(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口获得焦点事件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口失去焦点事件处理 + /// + private void OnWindowLostFocus(object sender, RoutedEventArgs e) + { + try + { + _isWindowFocused = false; + UpdateHotkeyStateBasedOnContext(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口失去焦点事件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 鼠标进入窗口事件处理 + /// + private void OnMouseEnterWindow(object sender, System.Windows.Input.MouseEventArgs e) + { + try + { + _isMouseOverWindow = true; + UpdateHotkeyStateBasedOnContext(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理鼠标进入窗口事件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 鼠标离开窗口事件处理 + /// + private void OnMouseLeaveWindow(object sender, System.Windows.Input.MouseEventArgs e) + { + try + { + _isMouseOverWindow = false; + UpdateHotkeyStateBasedOnContext(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理鼠标离开窗口事件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 鼠标位置定时器事件处理 + /// + private void OnMousePositionTimerTick(object sender, EventArgs e) + { + try + { + if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys) + return; + + // 检查鼠标是否在当前窗口所在的屏幕上 + var mousePosition = Control.MousePosition; + var currentScreen = Screen.FromPoint(mousePosition); + + // 无论屏幕是否变化,都检查热键状态 + // 这样可以确保热键状态始终与当前上下文保持一致 + bool shouldEnableHotkeys = ShouldEnableHotkeysBasedOnContext(); + bool currentlyHasHotkeys = _registeredHotkeys.Count > 0; + + if (shouldEnableHotkeys && !currentlyHasHotkeys) + { + UpdateHotkeyStateBasedOnContext(); + } + else if (!shouldEnableHotkeys && currentlyHasHotkeys) + { + UpdateHotkeyStateBasedOnContext(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理鼠标位置定时器事件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 根据上下文更新热键状态 + /// + private void UpdateHotkeyStateBasedOnContext() + { + try + { + if (!_hotkeysShouldBeRegistered) + return; + + bool shouldEnableHotkeys = ShouldEnableHotkeysBasedOnContext(); + + if (shouldEnableHotkeys) + { + // 如果热键未注册,则注册 + if (_registeredHotkeys.Count == 0) + { + LoadHotkeysFromSettings(); + } + } + else + { + // 如果热键已注册,则注销(与鼠标模式禁用保持一致) + if (_registeredHotkeys.Count > 0) + { + UnregisterAllHotkeys(); + + // 注意:这里不设置 _hotkeysShouldBeRegistered = false + // 因为我们需要保持热键系统的启用状态,只是暂时注销热键 + // 这样当上下文变化时,热键可以重新注册 + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"根据上下文更新热键状态时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 检查是否应该注册热键(基于屏幕和模式) + /// + /// 是否应该注册热键 + private bool ShouldRegisterHotkeys() + { + try + { + // 如果禁用热键注册,则不注册 + if (!_hotkeysShouldBeRegistered) + return false; + + // 如果启用基于屏幕的热键注册 + if (_enableScreenSpecificHotkeys && _isMultiScreenMode) + { + return ShouldEnableHotkeysBasedOnContext(); + } + + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查是否应该注册热键时出错: {ex.Message}", LogHelper.LogType.Error); + return true; // 出错时默认注册 + } + } + + /// + /// 根据上下文检查是否应该启用热键 + /// + /// 是否应该启用热键 + private bool ShouldEnableHotkeysBasedOnContext() + { + try + { + // 策略1:鼠标在窗口上时启用热键(最高优先级) + if (_isMouseOverWindow) + { + return true; + } + + // 策略2:在多屏幕环境下,检查鼠标是否在当前窗口所在的屏幕上 + if (_isMultiScreenMode) + { + var mousePosition = Control.MousePosition; + var mouseScreen = Screen.FromPoint(mousePosition); + + if (mouseScreen == _currentScreen) + { + return true; + } + else + { + return false; + } + } + + // 策略3:单屏幕环境下,窗口有焦点时启用热键 + if (_isWindowFocused) + { + return true; + } + + // 默认情况:禁用热键 + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查是否应该启用热键时出错: {ex.Message}", LogHelper.LogType.Error); + return true; // 出错时默认启用 + } + } + /// /// 切换到指定笔类型 /// @@ -513,7 +951,6 @@ namespace Ink_Canvas.Helpers // 如果配置文件不存在,创建默认配置文件 if (!File.Exists(HotkeyConfigFile)) { - LogHelper.WriteLogToFile($"快捷键配置文件不存在,创建默认配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event); CreateDefaultConfigFile(); } } @@ -579,7 +1016,6 @@ namespace Ink_Canvas.Helpers // 写入配置文件 File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8); - LogHelper.WriteLogToFile($"已创建默认快捷键配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -643,7 +1079,6 @@ namespace Ink_Canvas.Helpers } } - LogHelper.WriteLogToFile($"成功加载 {successCount}/{config.Hotkeys.Count} 个快捷键配置", LogHelper.LogType.Event); if (successCount > 0) { _hotkeysShouldBeRegistered = true; @@ -702,7 +1137,6 @@ namespace Ink_Canvas.Helpers // 直接写入原文件,覆盖原有内容 File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8); - LogHelper.WriteLogToFile($"快捷键配置已保存到: {HotkeyConfigFile}", LogHelper.LogType.Event); return true; } catch (Exception ex) @@ -908,6 +1342,29 @@ namespace Ink_Canvas.Helpers { if (!_isDisposed) { + // 注销所有快捷键 + UnregisterAllHotkeys(); + + // 停止定时器 + if (_mousePositionTimer != null) + { + _mousePositionTimer.Stop(); + _mousePositionTimer = null; + } + + // 移除事件监听器 + if (_mainWindow != null) + { + if (_isMultiScreenMode) + { + _mainWindow.LocationChanged -= OnWindowLocationChanged; + } + + _mainWindow.GotFocus -= OnWindowGotFocus; + _mainWindow.LostFocus -= OnWindowLostFocus; + _mainWindow.MouseEnter -= OnMouseEnterWindow; + _mainWindow.MouseLeave -= OnMouseLeaveWindow; + } _isDisposed = true; } diff --git a/Ink Canvas/Helpers/InkFadeManager.cs b/Ink Canvas/Helpers/InkFadeManager.cs index a13d1ea6..23d74ef5 100644 --- a/Ink Canvas/Helpers/InkFadeManager.cs +++ b/Ink Canvas/Helpers/InkFadeManager.cs @@ -71,6 +71,11 @@ namespace Ink_Canvas.Helpers try { + // 确保主窗口的InkCanvas保持Ink编辑模式,防止墨迹渐隐时切换到鼠标模式 + if (_mainWindow.inkCanvas.EditingMode != InkCanvasEditingMode.Ink) + { + _mainWindow.inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + } // 记录墨迹的起点和终点 _strokeStartPoints[stroke] = startPoint; diff --git a/Ink Canvas/Helpers/InkSmoothingConfig.cs b/Ink Canvas/Helpers/InkSmoothingConfig.cs index 6cb996bb..c1af933d 100644 --- a/Ink Canvas/Helpers/InkSmoothingConfig.cs +++ b/Ink Canvas/Helpers/InkSmoothingConfig.cs @@ -75,6 +75,9 @@ namespace Ink_Canvas.Helpers /// public void ApplyQualitySettings() { + // 保存用户设置的异步处理偏好 + bool userAsyncPreference = UseAsyncProcessing; + switch (Quality) { case SmoothingQuality.Performance: @@ -85,7 +88,7 @@ namespace Ink_Canvas.Helpers CurveTension = 0.15; MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2); UseHardwareAcceleration = true; - UseAsyncProcessing = true; + UseAsyncProcessing = userAsyncPreference; break; case SmoothingQuality.Balanced: @@ -96,7 +99,7 @@ namespace Ink_Canvas.Helpers CurveTension = 0.25; MaxConcurrentTasks = Environment.ProcessorCount; UseHardwareAcceleration = true; - UseAsyncProcessing = true; + UseAsyncProcessing = userAsyncPreference; break; case SmoothingQuality.Quality: @@ -107,7 +110,7 @@ namespace Ink_Canvas.Helpers CurveTension = 0.35; MaxConcurrentTasks = Environment.ProcessorCount; UseHardwareAcceleration = true; - UseAsyncProcessing = true; + UseAsyncProcessing = userAsyncPreference; break; } } diff --git a/Ink Canvas/Helpers/MultiPPTInkManager.cs b/Ink Canvas/Helpers/MultiPPTInkManager.cs index 29228802..ec491824 100644 --- a/Ink Canvas/Helpers/MultiPPTInkManager.cs +++ b/Ink Canvas/Helpers/MultiPPTInkManager.cs @@ -1,7 +1,6 @@ using Microsoft.Office.Interop.PowerPoint; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -27,6 +26,11 @@ namespace Ink_Canvas.Helpers private readonly object _lockObject = new object(); private bool _disposed; private string _currentActivePresentationId = ""; + + // 墨迹备份机制 + private readonly Dictionary> _strokeBackups; + private DateTime _lastBackupTime = DateTime.MinValue; + private const int BackupIntervalMinutes = 2; // 每2分钟备份一次 #endregion #region Constructor @@ -34,6 +38,7 @@ namespace Ink_Canvas.Helpers { _presentationManagers = new Dictionary(); _presentationInfos = new Dictionary(); + _strokeBackups = new Dictionary>(); } #endregion @@ -113,8 +118,15 @@ namespace Ink_Canvas.Helpers var currentPresentation = GetCurrentActivePresentation(); if (currentPresentation != null) { - currentManager.SaveAllStrokesToFile(currentPresentation); - LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace); + try + { + currentManager.SaveAllStrokesToFile(currentPresentation); + LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存当前演示文稿墨迹失败: {ex}", LogHelper.LogType.Error); + } } } } @@ -127,10 +139,10 @@ namespace Ink_Canvas.Helpers _presentationInfos[presentationId].LastAccessTime = DateTime.Now; } - if (_currentActivePresentationId != presentationId) - { - LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace); - } + if (_currentActivePresentationId != presentationId) + { + LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace); + } return true; } else @@ -162,7 +174,17 @@ namespace Ink_Canvas.Helpers var manager = GetCurrentManager(); if (manager != null) { + // 保存到管理器 manager.SaveCurrentSlideStrokes(slideIndex, strokes); + + // 只有在保存成功后才创建备份 + if (!string.IsNullOrEmpty(_currentActivePresentationId)) + { + CreateStrokeBackup(_currentActivePresentationId, slideIndex, strokes); + } + + // 检查是否需要执行定期备份 + CheckAndPerformBackup(); } } catch (Exception ex) @@ -210,12 +232,29 @@ namespace Ink_Canvas.Helpers var manager = GetCurrentManager(); if (manager != null) { - return manager.LoadSlideStrokes(slideIndex); + var strokes = manager.LoadSlideStrokes(slideIndex); + + // 如果从管理器加载失败,尝试从备份恢复 + if (strokes == null || strokes.Count == 0) + { + if (!string.IsNullOrEmpty(_currentActivePresentationId)) + { + strokes = RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex); + } + } + + return strokes ?? new StrokeCollection(); } } catch (Exception ex) { LogHelper.WriteLogToFile($"加载页面墨迹失败: {ex}", LogHelper.LogType.Error); + + // 尝试从备份恢复 + if (!string.IsNullOrEmpty(_currentActivePresentationId)) + { + return RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex); + } } } @@ -512,6 +551,12 @@ namespace Ink_Canvas.Helpers } _presentationInfos.Remove(id); + // 清理备份数据 + if (_strokeBackups.ContainsKey(id)) + { + _strokeBackups.Remove(id); + } + LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace); } } @@ -521,6 +566,96 @@ namespace Ink_Canvas.Helpers } } } + + /// + /// 创建墨迹备份 + /// + private void CreateStrokeBackup(string presentationId, int slideIndex, StrokeCollection strokes) + { + try + { + if (strokes == null || strokes.Count == 0) return; + + if (!_strokeBackups.ContainsKey(presentationId)) + { + _strokeBackups[presentationId] = new Dictionary(); + } + + // 释放旧的备份 + if (_strokeBackups[presentationId].ContainsKey(slideIndex)) + { + _strokeBackups[presentationId][slideIndex] = null; + } + + // 创建新的备份 + _strokeBackups[presentationId][slideIndex] = strokes.Clone(); + + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建墨迹备份失败: {ex}", LogHelper.LogType.Error); + } + } + + /// + /// 从备份恢复墨迹 + /// + private StrokeCollection RestoreStrokeFromBackup(string presentationId, int slideIndex) + { + try + { + if (_strokeBackups.ContainsKey(presentationId) && + _strokeBackups[presentationId].ContainsKey(slideIndex)) + { + var backup = _strokeBackups[presentationId][slideIndex]; + if (backup != null) + { + LogHelper.WriteLogToFile($"从备份恢复第{slideIndex}页墨迹", LogHelper.LogType.Trace); + return backup.Clone(); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从备份恢复墨迹失败: {ex}", LogHelper.LogType.Error); + } + + return new StrokeCollection(); + } + + /// + /// 检查并执行定期备份 + /// + private void CheckAndPerformBackup() + { + try + { + var now = DateTime.Now; + + // 检查是否需要执行备份 + if (now - _lastBackupTime < TimeSpan.FromMinutes(BackupIntervalMinutes)) + { + return; + } + + // 备份当前活跃演示文稿的所有墨迹 + if (!string.IsNullOrEmpty(_currentActivePresentationId) && + _presentationManagers.ContainsKey(_currentActivePresentationId)) + { + var manager = _presentationManagers[_currentActivePresentationId]; + if (manager != null) + { + // 这里可以添加更详细的备份逻辑 + } + } + + _lastBackupTime = now; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"定期备份检查失败: {ex}", LogHelper.LogType.Error); + } + } #endregion #region Private Methods @@ -562,7 +697,7 @@ namespace Ink_Canvas.Helpers var presentationPath = presentation.FullName; var fileHash = GetFileHash(presentationPath); var processId = GetProcessId(presentation); - return $"{presentation.Name}_{fileHash}_{processId}"; + return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}_{processId}"; } catch (COMException comEx) { @@ -638,12 +773,24 @@ namespace Ink_Canvas.Helpers { lock (_lockObject) { + // 释放所有管理器 foreach (var manager in _presentationManagers.Values) { manager?.Dispose(); } _presentationManagers.Clear(); _presentationInfos.Clear(); + + // 清理备份数据 + foreach (var backupDict in _strokeBackups.Values) + { + foreach (var backup in backupDict.Values) + { + backup?.Clear(); + } + backupDict.Clear(); + } + _strokeBackups.Clear(); } _disposed = true; } @@ -663,4 +810,4 @@ namespace Ink_Canvas.Helpers public DateTime CreatedTime { get; set; } public DateTime LastAccessTime { get; set; } } -} \ No newline at end of file +} diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs index 0776e827..2fee446c 100644 --- a/Ink Canvas/Helpers/PPTInkManager.cs +++ b/Ink Canvas/Helpers/PPTInkManager.cs @@ -35,6 +35,12 @@ namespace Ink_Canvas.Helpers private DateTime _lastSwitchTime = DateTime.MinValue; private int _lastSwitchSlideIndex = -1; private const int MinSwitchIntervalMs = 100; // 最小切换间隔100毫秒 + + // 内存管理相关字段 + private long _totalMemoryUsage = 0; + private const long MaxMemoryUsageBytes = 100 * 1024 * 1024; // 100MB限制 + private DateTime _lastMemoryCleanup = DateTime.MinValue; + private const int MemoryCleanupIntervalMinutes = 5; // 5分钟清理一次 #endregion #region Constructor @@ -118,24 +124,34 @@ namespace Ink_Canvas.Helpers { if (DateTime.Now < _inkLockUntil) { - LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning); } return; } if (slideIndex < _memoryStreams.Length) { + // 先释放旧的内存流,防止内存泄漏 + try + { + _memoryStreams[slideIndex]?.Dispose(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning); + } + + // 创建新的内存流 var ms = new MemoryStream(); strokes.Save(ms); ms.Position = 0; - - // 释放旧的内存流 - _memoryStreams[slideIndex]?.Dispose(); _memoryStreams[slideIndex] = ms; if (ms.Length > 0) { } + + // 检查内存使用情况 + CheckAndPerformMemoryCleanup(); } } catch (Exception ex) @@ -158,12 +174,20 @@ namespace Ink_Canvas.Helpers { if (slideIndex < _memoryStreams.Length) { + // 先释放旧的内存流,防止内存泄漏 + try + { + _memoryStreams[slideIndex]?.Dispose(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning); + } + + // 创建新的内存流 var ms = new MemoryStream(); strokes.Save(ms); ms.Position = 0; - - // 释放旧的内存流 - _memoryStreams[slideIndex]?.Dispose(); _memoryStreams[slideIndex] = ms; LogHelper.WriteLogToFile($"已强制保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace); @@ -388,13 +412,30 @@ namespace Ink_Canvas.Helpers { try { - for (int i = 0; i < _memoryStreams.Length; i++) + // 安全释放所有内存流 + if (_memoryStreams != null) { - _memoryStreams[i]?.Dispose(); - _memoryStreams[i] = null; + for (int i = 0; i < _memoryStreams.Length; i++) + { + try + { + _memoryStreams[i]?.Dispose(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放内存流{i}失败: {ex}", LogHelper.LogType.Warning); + } + finally + { + _memoryStreams[i] = null; + } + } + + // 重新初始化数组 + _memoryStreams = new MemoryStream[_maxSlides + 2]; } - CurrentStrokes.Clear(); + CurrentStrokes?.Clear(); LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace); } catch (Exception ex) @@ -430,6 +471,13 @@ namespace Ink_Canvas.Helpers return true; } + // 如果当前页面不是锁定页面,但锁定时间很短(小于50ms),允许写入 + // 这样可以确保旧页面的墨迹能够及时保存 + if (DateTime.Now - (_inkLockUntil.AddMilliseconds(-InkLockMilliseconds)) < TimeSpan.FromMilliseconds(50)) + { + return true; + } + // 只有在快速切换且页面不同时才锁定 return false; } @@ -447,6 +495,110 @@ namespace Ink_Canvas.Helpers _lastSwitchSlideIndex = -1; } } + + /// + /// 检查并执行内存清理 + /// + private void CheckAndPerformMemoryCleanup() + { + try + { + var now = DateTime.Now; + + // 检查是否需要执行内存清理 + if (now - _lastMemoryCleanup < TimeSpan.FromMinutes(MemoryCleanupIntervalMinutes)) + { + return; + } + + // 计算当前内存使用量 + long currentMemoryUsage = 0; + if (_memoryStreams != null) + { + for (int i = 0; i < _memoryStreams.Length; i++) + { + if (_memoryStreams[i] != null) + { + currentMemoryUsage += _memoryStreams[i].Length; + } + } + } + + _totalMemoryUsage = currentMemoryUsage; + + // 如果内存使用量超过限制,执行清理 + if (currentMemoryUsage > MaxMemoryUsageBytes) + { + LogHelper.WriteLogToFile($"内存使用量超限 ({currentMemoryUsage / 1024 / 1024}MB),开始清理", LogHelper.LogType.Warning); + + // 清理非当前页面的墨迹 + CleanupInactiveSlideStrokes(); + + _lastMemoryCleanup = now; + LogHelper.WriteLogToFile($"内存清理完成,当前使用量: {_totalMemoryUsage / 1024 / 1024}MB", LogHelper.LogType.Trace); + } + else + { + _lastMemoryCleanup = now; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"内存清理检查失败: {ex}", LogHelper.LogType.Error); + } + } + + /// + /// 清理非活跃页面的墨迹 + /// + private void CleanupInactiveSlideStrokes() + { + try + { + if (_memoryStreams == null) return; + + int cleanedCount = 0; + long freedMemory = 0; + + for (int i = 0; i < _memoryStreams.Length; i++) + { + // 保留当前锁定页面和最近访问的页面 + if (i == _lockedSlideIndex || i == _lastSwitchSlideIndex) + { + continue; + } + + if (_memoryStreams[i] != null) + { + long memorySize = _memoryStreams[i].Length; + + try + { + _memoryStreams[i].Dispose(); + freedMemory += memorySize; + cleanedCount++; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理页面{i}墨迹失败: {ex}", LogHelper.LogType.Warning); + } + finally + { + _memoryStreams[i] = null; + } + } + } + + if (cleanedCount > 0) + { + LogHelper.WriteLogToFile($"已清理{cleanedCount}个页面的墨迹,释放内存: {freedMemory / 1024}KB", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理非活跃页面墨迹失败: {ex}", LogHelper.LogType.Error); + } + } #endregion #region Private Methods @@ -456,7 +608,7 @@ namespace Ink_Canvas.Helpers { var presentationPath = presentation.FullName; var fileHash = GetFileHash(presentationPath); - return $"{presentation.Name}_{fileHash}"; + return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}"; } catch (Exception ex) { diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index ecca9519..11e1bbed 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -93,7 +93,6 @@ namespace Ink_Canvas.Helpers // COM对象已失效,触发断开连接 DisconnectFromPPT(); } - LogHelper.WriteLogToFile($"验证PPT放映窗口失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); return false; } } @@ -411,63 +410,30 @@ namespace Ink_Canvas.Helpers catch (COMException comEx) { var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"取消PPT事件注册时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); } catch (InvalidCastException) { // COM对象类型转换失败,通常是因为对象已经被释放 LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace); } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning); + } }, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1)); } } - catch (Exception) + catch (Exception ex) { + LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex}", LogHelper.LogType.Warning); } - // 释放COM对象 - try - { - if (Marshal.IsComObject(CurrentSlide)) - { - Marshal.ReleaseComObject(CurrentSlide); - } - } - catch (Exception) - { - } - - try - { - if (Marshal.IsComObject(CurrentSlides)) - { - Marshal.ReleaseComObject(CurrentSlides); - } - } - catch (Exception) - { - } - - try - { - if (Marshal.IsComObject(CurrentPresentation)) - { - Marshal.ReleaseComObject(CurrentPresentation); - } - } - catch (Exception) - { - } - - try - { - if (Marshal.IsComObject(PPTApplication)) - { - Marshal.ReleaseComObject(PPTApplication); - } - } - catch (Exception) - { - } + // 安全释放COM对象 + SafeReleaseComObject(CurrentSlide, "CurrentSlide"); + SafeReleaseComObject(CurrentSlides, "CurrentSlides"); + SafeReleaseComObject(CurrentPresentation, "CurrentPresentation"); + SafeReleaseComObject(PPTApplication, "PPTApplication"); // 清理引用 PPTApplication = null; @@ -491,6 +457,30 @@ namespace Ink_Canvas.Helpers } } + /// + /// 安全释放COM对象 + /// + private void SafeReleaseComObject(object comObject, string objectName) + { + try + { + if (comObject != null && Marshal.IsComObject(comObject)) + { + int refCount = Marshal.ReleaseComObject(comObject); + LogHelper.WriteLogToFile($"已释放COM对象 {objectName},引用计数: {refCount}", LogHelper.LogType.Trace); + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning); + } + } + private void UpdateCurrentPresentationInfo() { try @@ -859,10 +849,23 @@ namespace Ink_Canvas.Helpers // 如果在放映模式,获取放映窗口的演示文稿 if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0) { - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow?.View != null) + try { - return (Presentation)slideShowWindow.View.Slide.Parent; + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + return (Presentation)slideShowWindow.View.Slide.Parent; + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x80048240) // Integer out of range + { + // 放映窗口已不存在,返回null + return null; + } + throw; // 重新抛出其他COM异常 } } @@ -935,12 +938,10 @@ namespace Ink_Canvas.Helpers // COM对象已失效,触发断开连接 DisconnectFromPPT(); } - LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {comEx.Message}", LogHelper.LogType.Warning); return 0; } - catch (Exception ex) + catch (Exception) { - LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {ex}", LogHelper.LogType.Error); return 0; } } diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs index fef41177..c8482a4e 100644 --- a/Ink Canvas/Helpers/PPTUIManager.cs +++ b/Ink Canvas/Helpers/PPTUIManager.cs @@ -92,7 +92,6 @@ namespace Ink_Canvas.Helpers // 页数无效时清空页码显示 _mainWindow.PPTBtnPageNow.Text = "?"; _mainWindow.PPTBtnPageTotal.Text = "/ ?"; - LogHelper.WriteLogToFile($"PPT页数无效,清空页码显示: 当前页={currentSlide}, 总页数={totalSlides}", LogHelper.LogType.Warning); } UpdateNavigationPanelsVisibility(); @@ -132,7 +131,6 @@ namespace Ink_Canvas.Helpers // 页数无效时清空页码显示 _mainWindow.PPTBtnPageNow.Text = "?"; _mainWindow.PPTBtnPageTotal.Text = "/ ?"; - LogHelper.WriteLogToFile($"PPT页数无效,清空页码显示: 当前页={currentSlide}, 总页数={totalSlides}", LogHelper.LogType.Warning); } } catch (Exception ex) @@ -182,7 +180,8 @@ namespace Ink_Canvas.Helpers bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible && isInSlideShow && - hasValidPageCount; + hasValidPageCount && + !MainWindow.Settings.Automation.IsAutoFoldInPPTSlideShow; if (!shouldShowButtons) { diff --git a/Ink Canvas/Helpers/ScreenDetectionHelper.cs b/Ink Canvas/Helpers/ScreenDetectionHelper.cs new file mode 100644 index 00000000..5d048a7f --- /dev/null +++ b/Ink Canvas/Helpers/ScreenDetectionHelper.cs @@ -0,0 +1,156 @@ +using System; +using System.Drawing; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Interop; +using Point = System.Windows.Point; + +namespace Ink_Canvas.Helpers +{ + /// + /// 屏幕检测帮助类 - 用于检测窗口所在的屏幕和屏幕信息 + /// + public static class ScreenDetectionHelper + { + /// + /// 获取窗口所在的屏幕 + /// + /// 要检测的窗口 + /// 窗口所在的屏幕,如果无法检测则返回主屏幕 + public static Screen GetWindowScreen(Window window) + { + try + { + if (window == null) + return Screen.PrimaryScreen; + + // 获取窗口的句柄 + var hwndSource = PresentationSource.FromVisual(window) as HwndSource; + if (hwndSource == null) + return Screen.PrimaryScreen; + + // 获取窗口在屏幕上的位置 + var windowRect = GetWindowScreenBounds(window); + + // 查找与窗口重叠最多的屏幕 + Screen targetScreen = null; + double maxIntersection = 0; + + foreach (var screen in Screen.AllScreens) + { + var intersection = Rectangle.Intersect(windowRect, screen.Bounds); + if (intersection.Width * intersection.Height > maxIntersection) + { + maxIntersection = intersection.Width * intersection.Height; + targetScreen = screen; + } + } + + return targetScreen ?? Screen.PrimaryScreen; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检测窗口屏幕时出错: {ex.Message}", LogHelper.LogType.Warning); + return Screen.PrimaryScreen; + } + } + + /// + /// 获取窗口在屏幕坐标系中的边界 + /// + /// 要检测的窗口 + /// 窗口的屏幕边界 + private static Rectangle GetWindowScreenBounds(Window window) + { + try + { + // 获取窗口左上角在屏幕上的位置 + var topLeft = window.PointToScreen(new Point(0, 0)); + + // 获取窗口右下角在屏幕上的位置 + var bottomRight = window.PointToScreen(new Point(window.ActualWidth, window.ActualHeight)); + + return new Rectangle( + (int)topLeft.X, + (int)topLeft.Y, + (int)(bottomRight.X - topLeft.X), + (int)(bottomRight.Y - topLeft.Y)); + } + catch + { + // 如果无法获取精确位置,返回窗口的Left和Top + return new Rectangle( + (int)window.Left, + (int)window.Top, + (int)window.Width, + (int)window.Height); + } + } + + /// + /// 检查是否有多个屏幕 + /// + /// 如果有多个屏幕返回true,否则返回false + public static bool HasMultipleScreens() + { + try + { + return Screen.AllScreens.Length > 1; + } + catch + { + return false; + } + } + + /// + /// 获取主屏幕 + /// + /// 主屏幕 + public static Screen GetPrimaryScreen() + { + try + { + return Screen.PrimaryScreen; + } + catch + { + return null; + } + } + + /// + /// 获取所有屏幕信息 + /// + /// 所有屏幕的数组 + public static Screen[] GetAllScreens() + { + try + { + return Screen.AllScreens; + } + catch + { + return new Screen[] { Screen.PrimaryScreen }; + } + } + + /// + /// 检查窗口是否在主屏幕上 + /// + /// 要检查的窗口 + /// 如果窗口在主屏幕上返回true,否则返回false + public static bool IsWindowOnPrimaryScreen(Window window) + { + try + { + var windowScreen = GetWindowScreen(window); + return windowScreen == Screen.PrimaryScreen; + } + catch + { + return true; // 出错时假设在主屏幕 + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 8d67e1d4..5ff02806 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -208,6 +208,7 @@ + @@ -492,6 +493,15 @@ + + + + + + + + + @@ -556,6 +566,9 @@ + + + diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 187411e7..e9495e55 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -22,7 +22,7 @@ Height="9000" Width="1440" FontFamily="Microsoft YaHei UI" MouseWheel="Window_MouseWheel" - Foreground="Black" + Foreground="{DynamicResource FloatBarForeground}" SizeChanged="MainWindow_OnSizeChanged" MouseMove="MainWindow_OnMouseMove" Stylus.IsPressAndHoldEnabled="False" @@ -186,6 +186,21 @@ + + + - - + + - + - + - - @@ -1028,6 +1029,19 @@ FontSize="26" /> + + + + + + + + + @@ -1192,6 +1206,15 @@ + + + + + + @@ -2319,6 +2342,28 @@ + + + + + + + + + + + + + + + + + @@ -91,18 +91,18 @@ @@ -244,3 +244,4 @@ + diff --git a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs b/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs index a90e6b1d..f3fd787f 100644 --- a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs +++ b/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs @@ -1,4 +1,5 @@ using Ink_Canvas.Helpers; +using Ink_Canvas.Resources; using System; using System.Media; using System.Timers; @@ -6,6 +7,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; +using System.Windows.Media.Imaging; namespace Ink_Canvas { @@ -21,6 +23,7 @@ namespace Ink_Canvas timer.Elapsed += Timer_Elapsed; timer.Interval = 50; + InitializeUI(); } private void Timer_Elapsed(object sender, ElapsedEventArgs e) @@ -62,13 +65,13 @@ namespace Ink_Canvas Application.Current.Dispatcher.Invoke(() => { //Play sound - player.Stream = Properties.Resources.TimerDownNotice; - player.Play(); + PlayTimerSound(); }); } } SoundPlayer player = new SoundPlayer(); + MediaPlayer mediaPlayer = new MediaPlayer(); int hour = 0; int minute = 1; @@ -80,6 +83,7 @@ namespace Ink_Canvas bool isTimerRunning = false; bool isPaused = false; + bool useLegacyUI = false; Timer timer = new Timer(); @@ -334,6 +338,97 @@ namespace Ink_Canvas } } + private void InitializeUI() + { + // 从设置中读取配置 + if (MainWindow.Settings.RandSettings != null) + { + useLegacyUI = MainWindow.Settings.RandSettings.UseLegacyTimerUI; + UpdateButtonTexts(); + } + } + + public void RefreshUI() + { + InitializeUI(); + } + + private void UpdateButtonTexts() + { + if (useLegacyUI) + { + // 老版UI:使用+5, +1, -1, -5 + HourPlus5Text.Text = "+5"; + HourPlus1Text.Text = "+1"; + HourMinus1Text.Text = "-1"; + HourMinus5Text.Text = "-5"; + + MinutePlus5Text.Text = "+5"; + MinutePlus1Text.Text = "+1"; + MinuteMinus1Text.Text = "-1"; + MinuteMinus5Text.Text = "-5"; + + SecondPlus5Text.Text = "+5"; + SecondPlus1Text.Text = "+1"; + SecondMinus1Text.Text = "-1"; + SecondMinus5Text.Text = "-5"; + } + else + { + // 新版UI:使用箭头符号 + HourPlus5Text.Text = "∧∧"; + HourPlus1Text.Text = "∧"; + HourMinus1Text.Text = "∨"; + HourMinus5Text.Text = "∨∨"; + + MinutePlus5Text.Text = "∧∧"; + MinutePlus1Text.Text = "∧"; + MinuteMinus1Text.Text = "∨"; + MinuteMinus5Text.Text = "∨∨"; + + SecondPlus5Text.Text = "∧∧"; + SecondPlus1Text.Text = "∧"; + SecondMinus1Text.Text = "∨"; + SecondMinus5Text.Text = "∨∨"; + } + } + + private void PlayTimerSound() + { + try + { + double volume = MainWindow.Settings.RandSettings?.TimerVolume ?? 1.0; + mediaPlayer.Volume = volume; + + if (!string.IsNullOrEmpty(MainWindow.Settings.RandSettings?.CustomTimerSoundPath) && + System.IO.File.Exists(MainWindow.Settings.RandSettings.CustomTimerSoundPath)) + { + // 播放自定义铃声 + mediaPlayer.Open(new Uri(MainWindow.Settings.RandSettings.CustomTimerSoundPath)); + } + else + { + // 播放默认铃声 + string tempPath = System.IO.Path.GetTempFileName() + ".wav"; + using (var stream = Properties.Resources.TimerDownNotice) + { + using (var fileStream = new System.IO.FileStream(tempPath, System.IO.FileMode.Create)) + { + stream.CopyTo(fileStream); + } + } + mediaPlayer.Open(new Uri(tempPath)); + } + + mediaPlayer.Play(); + } + catch (Exception ex) + { + // 如果播放失败,静默处理 + System.Diagnostics.Debug.WriteLine($"播放计时器铃声失败: {ex.Message}"); + } + } + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { isTimerRunning = false; diff --git a/Ink Canvas/Windows/HasNewUpdateWindow.xaml b/Ink Canvas/Windows/HasNewUpdateWindow.xaml index 61d8f5d9..bc7620b0 100644 --- a/Ink Canvas/Windows/HasNewUpdateWindow.xaml +++ b/Ink Canvas/Windows/HasNewUpdateWindow.xaml @@ -7,27 +7,247 @@ xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml" mc:Ignorable="d" - ui:WindowHelper.UseModernWindowStyle = "True" + ui:WindowHelper.UseModernWindowStyle="False" ui:WindowHelper.SystemBackdropType="Mica" - ui:TitleBar.Height="36" - Title="InkCanvasForClass CE有新版本可用" Height="600" Width="850" ResizeMode="NoResize" - WindowStartupLocation="CenterScreen"> + Title="InkCanvasForClass CE有新版本可用" Height="650" Width="900" ResizeMode="NoResize" + WindowStartupLocation="CenterScreen" WindowStyle="None" + Background="Transparent"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + # InkCanvasForClass v5.0.2更新 你好,旅行者们,本次InkCanvasForClass Community Edition更新带来了如下新功能供您探索: @@ -42,48 +262,83 @@ 8. 带来了基于FitToCurve的笔迹平滑,基于贝塞尔曲线平滑,让墨迹线条更加优美好看。 + - - - - - + + + + + + + + + + + + + + - - - - + + + + + + + + + - + + + + + + + + + -